{{announcement.body}}
{{announcement.title}}

3 Reasons Mobile Apps Can Be Slow

DZone 's Guide to

3 Reasons Mobile Apps Can Be Slow

Instead of going back and forth between cross-platform and native frameworks, explore these other reasons your mobile app may be crawling.

· Performance Zone ·
Free Resource


Slowness comes in two flavors. One flavor includes loading screens, waiting for UI to build up, etc. The other form of slowness involves the UX — such as buttons not being as responsive as expected, default gestures, actions and animations not happening when they are expected to be happening. Both contribute to a bad user experience. In this blog post, I'll dive into the primary causes for an app to feel slow and how you can prevent most of them with a focus on cross-platform app frameworks.

Cross-Platform vs. Native

I often hear people complain about cross-platform technologies. Most of the complainers say cross-platform technologies are slow, not a match for native, web-based, for prototypes only, inferior to native, and guaranteed to fail.

All of the above can apply to cross-platform frameworks, but all of them are just as likely to be applied to native apps, as well. It usually depends on the user implementing the code.

In fact, I would say it is easier to develop a crashing and slow application natively (Swift/Java) than it is to build one using cross-platform technologies like Titanium. What you should also understand is that cross-platform frameworks generate actual native UI. (This is not the case with hybrid cross-platform tools.)

A lot of the problems you encounter as a native developer, such as memory leaks, are mostly solved in cross-platform frameworks. Most of the heavy lifting is already done for you by the developers of said framework.

Understanding limitations of the frameworks you are using is key in understanding what makes an app slow and how to avoid that. I will try to explain how cross-platform apps can behave unresponsively and slowly, as well as to understanding how to make them faster. As code examples, I will use Titanium as that is my framework of choice, but you should be able to apply the same "tricks" in any other framework.

Slowness Cause #1: The Design(er)

Often, when a team is developing a cross-platform app, the mindset changes from developing two apps to a single app. This understanding is incorrect and a root cause for making apps slow. Coming up with a single design for both iOS and Android will cause more trouble in the long run than you might expect. This idea is especially important to understand for designers as they are often the reason cross-platform designs have to be implemented.

The default UI and (more importantly) UX are completely different in both platforms — more than you would think at first. For example, iOS has a Window "stack" with opening and closing animations and slide gestures. These might seem trivial, but users will expect these animations to happen, and they will expect certain interactions to work. Sliding your finger over your phone with the idea that you can go "back" in a Window stack, only coming to the conclusion that it doesn't work and then having to click the back button manually is a thing that will make people think the app is slow.

When a window is designed to look differently, the developer will have to implement something that is not natively supported. And, because there will be a custom implementation of a window, the above gesture for closing a window will not work. Now, of course, you can implement an event listener to verify whether or not a user is sliding a finger across the screen — and then you can close the window. But, then all of a sudden your entire UX is gone, and users will perceive the app, again, as slow.

Not only is there a downside for the animations not working or the UI not looking the same as it should, but things like garbage collection will need to be implemented manually as well. Where this is normally handled internally, with all the custom UI going on, there will probably be memory leaks all over the place and you, the app developer, are charged with finding these.

Two years down the line if you still have this setup, you will probably have cut many more corners in design. No native buttons, extra custom gestures, more calculations of finger tracking — and your app will get slow overall. Animations are not as smooth as they can be, data loading is blocked by other calculations and vice versa, and shortcuts load data in steps so it "behaves." And, now, you've reached the point we tried to avoid in the first place. You have a slow and unresponsive app because you've cut corners — mainly because the design required that of you. One year further down the line and your app is so slow that you will completely have to rewrite the application, because it is no longer maintainable/usable despite the fact phones have become faster over the years.

Key takeaway: Custom implementation of native elements might seem like a good shortcut to develop the app faster. However, in the long run, it will make your app slow overall and unmaintainable. Not to mention — the UX is not what users expect and reviews of your app will dwindle.

The Solution

First of all, your designer will have to understand the differences between the platforms and embrace them. Don't cut corners and understand as a developer that not everything works the same on both platforms. Test implementations with users on both platforms. You will find that users of different platforms will expect different thing (the above example is only one of many examples). And, for this step, you don't even need to look outside of the company. Chances are there is at least one person in your company with an Android phone and one with an iPhone.

Try to take advantage of the built-in UX/UI features and think how you can implement your features using those of the native platform. iOS can have buttons left and right of the title — and this is expected of most apps. Android usually puts buttons on the right or hides them behind a menu icon.
Android has a built-in back button whereas iOS has a gesture to go back. Supporting these features allows users to intuitively understand your application.

The key understanding here is that using these features will benefit your app performance in the long run. In the short term, it might not be noticeable besides the fact that native UX won't work or your UI looks "off." But, there is a reason I listed this cause first. Having a good base of the app is more important than any other cause. Of course, other causes can make apps slower than cause #1, but without a good base, your app is doomed from the start no matter how well your intentions on other causes are.

Slowness Cause #2: Rendering Too Much

One of the biggest reasons apps become too slow is when you're rendering too much at a single point in time. With UI-rendering, it is important to keep two important things in mind.

Devices are slow and will have trouble rendering many items at once. And, of course, I don't mean the latest iPhone or high-end Android. I mean those people that still have Android phones running Android 4.4. And, while they might be used to slow apps, you want to keep your apps as fast as possible. A faster app might even help your app stand out from others!

Take the iOS App Store, for example. When you boot it up, you start on the "Today" tab. This tab is loaded after the tabs themselves are loaded. Try it yourself on your iPhone to see how it looks. You'll notice the "Today" tab being empty, but you will already see the five tabs on the bottom being loaded. This means the app can be fully open in a very small amount of time, and after that part is done, part of the data is loaded. Next step, start scrolling in the Today tab and pay attention to the scroll bar position and size. You'll see that based on your scrolling you should reach the end quite quickly, but as soon as you hit the 80% mark on scroll height you'll notice the scrollbar shrinks in size and position is altered. A second "page" of data is loaded at that point.

Next, when switching to a different tab, you will notice that only the header is present with the name of the tab and your profile picture. Content is loaded right after the window is opened. And even though the apps/games tabs look very big and full, not a lot of content is actually shown. One app element contains only a handful of UI elements: one image, three labels and a button. These elements can be rendered insanely quick. There's also a reason you're not seeing the entire top 200 apps in that list and you have to press the "see all" button to load more.

So, what can we learn from the App Store? The App Store doesn't load all the data at once. Imagine this: what if when the App Store booted up, loaded data for all five tabs, and rendered it on screen before the splash screen is even gone. Can you imagine how slow the app boot would be? It probably would take 1-2 seconds longer, if not more, for the app to boot. Now, imagine if they loaded ten times as much data for the Today page and also pre-loaded all images in there. You would probably look at a couple extra seconds boot time alone.

Ridiculous, right? Well, this is actually the way a lot, if not most, of apps are designed and built. Just think about the last app you published. Did you take rendering other tabs later into account? Did you postpone rendering the first tab the user sees? How much data did you pre-load, and how much data did you display in a ListView? 100 items? 200 items? How many API calls did you initiate when the app booted? I've even heard the question before, "How do I do 50 API calls most efficiently on app startup," and that wasn't about pre-loading certain data only once — that was the default startup-flow.

More than once, surprisingly, I noticed a question on TiSlack asking how to properly render 10,000 items in a ListView. The answer is always: DO NOT DO IT. Because there is not a single user, ever, who will look at all those items at once. If you want someone able to scroll through a list, use lazy-loading and pagination — even if you already have the data locally. Want to allow search within the items? Search within your local dataset, not on the rendered items.

So, How Do You Implement This?

What can you learn from the above? First of all, the app is opened first, and when that is done, the content is loaded. How would you implement such a thing? Take a default TabGroup:

<TabGroup >
 <Tab title="Today">
 <Window onPostlayout="handlePostlayout" id="todayWindow" />
 </Tab>
 <Tab></Tab>
 ...
</TabGroup>


The above code is meant to represent the App Store. I added a postlayout event listener to the Window in the first Tab. What this function is supposed to do is call the window initializer, which should fetch the relevant data for the Tab and then render it. By waiting for the postlayout event to fire, you will make sure the app is already on screen and the user no longer has to watch your splash screen. An easy way to initialize the content later would be to do something like this in the handlePostlayout function:

function handlePostlayout() {
  $.todayWindow.add(
    Alloy.createController('todayContent').getView() 
  );
}

You can see here that none of the logic actually required for the content of the Today tab is initialized or rendered, nor are any dependencies loaded. This makes an app extremely fast and there now is a really easy way to remove the content and re-add it as well as an extra bonus.

For any other tab than the first, monitor the "focus" event of a Window and do your rendering when a Window is focused. That way, you could even use that same event for refreshing data when a new focus is triggered later or use this event for things like Firebase Analytics.

Pagination and lazy loading can be implemented in many ways as well, but the main thing to keep in mind is that pages should be small, and the next dataset should be rendered relatively fast. You might, for example, want to download the data for page 2 as soon as the data of page 1 is rendered or when the user starts scrolling. Don't render it though, you might not need to render it and you will only waste cpu power doing so. Downloading data is relatively light compared to initializing a full page of data.

Slowness Cause #3: The Bridge

The bridge is a concept that exists in cross-platform frameworks like Titanium and React Native. What it means is that every time there is an interaction between your JavaScript code and the native code there is going to be overhead. For illustrative purposes, you could compare it with doing an API call to your server. Of course, the overhead is quite small compared to that, but there is going to be overhead regardless. When fetching 100 items from your server you would prefer to do one API call to fetch them all instead of doing 100 API calls to fetch them one by one. With the bridge, this same thing exists.

When is this bridge crossed? Well, basically, for any interaction between your code and the native layer, the bridge needs to be crossed. So, when adding UI elements, updating UI elements, triggering animations, etc. One thing that also crosses the bridge in Titanium that most people don't realize is the Ti.App.fireEvent flow. This event is usually used for triggering things within an application to initialize something. Very often, an event like that triggers the update of the UI, which is also a bridge crossing. And, now, a single event triggers two bridge crossings best case, instead of the very much wanted for one crossing.

With all these things going on at the same time, especially when triggered within loops, a bridge becomes crowded really fast. And, because it is a narrow bridge, you'll get tons of delays, slow moving traffic and, if you're unlucky, maybe even some accidents. One thing is for sure, your app performance will suffer. Especially on the slower devices, which are pretty much unavoidable for any production app.

The solution to this problem is actually quite simple. Try to batch together changes to the UI. When needing to change a whole list of ListItems, it is easier to re-insert the entire dataset at once (listSection.replaceItemsAt). Or, when you need to change a bunch of properties of a single UI element, use applyProperties instead of changing every property sequential.

Then, of course, use Backbone Events to trigger events throughout your app without having to cross any bridge. It's really simple to use and also really easy to migrate your current Ti.App events to backbone events.

First, include backbone events in alloy.js:

Alloy.Globals.events = _.clone(Backbone.Events);


Then, anywhere in your app you previously used  Ti.App.fireEvent(), you can now replace this with:

Alloy.Globals.events.trigger();


And, in the same way, you can replace Ti.App.addEventListener()   with:

Alloy.Globals.events.on()


You could even write a global search and replace for your project, and you'll have an instant performance boost in your app.

Conclusion

There are many reasons why an app can be slow, and pretty much all of them are in control of you or your team. The framework of choice rarely is the cause of the slowness, but rather the implementation or inefficient code. Stick to native UI components, load as little data as possible and cross the bridge the least amount of times and you'll have already prevented the most common reasons why apps are slow.

Of course, there are many more reasons why an app can be slow. But, if you follow the above recommendations, most of those should be handled.

And, last but not least, cross-platform frameworks are not by definition slower than native apps. In fact, I would say 95-99% of the apps in the app store right now (excluding games) could've been built using frameworks like Titanium — and no one would've noticed. So, think about your next app and what tools you want to use, but don't exclude a cross-platform framework based on performance.

Topics:
performance ,mobile apps ,cross platform app ,app design ,bridge ,rendering speed

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}