Slowness comes in two flavors. One flavor includes loading screens, waiting for UI to build up, etc. The other form of slowness is 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 bad user experience. In this blog post, I will dive into the primary factors that cause 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 React Native, NativeScript, or Titanium.

What you should also understand is cross platform frameworks generate actual native UI. This is not the case with Ionic/Phonegap, and that is something you should know before picking up a cross platform framework.

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 unresponsive and slow, as well as to understanding how to make them faster. As code examples I will use Titanium as that is my frameworks 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 2 apps to 1 app. This understanding is incorrect and a root cause for making apps slow. When taking this one step further and coming up with a single design for both iOS and Android, this will cause more trouble in the long run than you might expect. This 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 from 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 you can go “back” in a Window stack, only coming to the conclusion 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 wether 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 and 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 not being as smooth as they can be, data loading is blocked by other calculations and vise versa, and shortcuts being made to 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 you’ve worked with required that of you. One year further down the line and your app is so slow 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.

So how then?

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 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 things, and the above example is only one of many examples of that. 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.

Actually 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 the buttons on the right, or hides them behind a menu icon.

Android has a build-in back button whereas iOS has a gesture to go back. Supporting them will allow users to intuitively understand your application.

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 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 2 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 that they bought for €100 at the time. And while they might be used to slow apps, you want to keep your apps as fast as possible. This 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, and this 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 5 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 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 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, 3 labels and a button. This can be rendered insanely quick. There also is 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 this? We can obviously see here App Store doesn’t load all the data at once. Image this: When the App Store boots up, it will load data for all 5 tabs and render 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 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’ve published. Did you take rendering other tabs later into account? Or postponing even rendering the first tab the user sees? How much data do you pre-load, and how much data do you display in a ListView ? 100 items? 200 items? How many API calls do you initiate when the app boots? I’ve even heard the question before “how do I do 50 API calls most efficiently on app-start”, 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: NOT. 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 to implement this flow?

So what can you learn from this. 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 is meant to represent the AppStore. 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, 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 focussed. 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 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 on 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 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.

So when is this bridge crossed? Well basically 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 cross 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 2 bridge crossings best case, instead of the very much wanted for one crossing.

And 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 needing 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 is 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&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 to most common reasons why apps are slow.

Of course there are many more reasons why an app can be slow, but with the learnings of above most of those you should be able to figure out already.

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 or React Native 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.