With users splitting between different platforms (e.g. Android, iOS, WP) and constant framework changes, it became quite challenging and tedious to keep up with all the changes.
For simple apps that are mostly presentational, it suits perfectly well. However, once we start digging deeper to exploit the potential of devices and native APIs, Devs tend to get into the loop of creating custom implementations, targeted specifically for iOS or Android devices.
Having this done, brings us back to the same amount or even more work required as if we were natively building apps.
Here I will describe one way in which apps can be made, regardless of the framework. I found it suitable for most projects I worked on. It relies on different concepts, from the usual MVVM or MVC pattern, but quite similar in ways. First and foremost, we have to understand the lifecycle of any mobile application.
It consists of events like launching, pausing, suppressing to the background, resuming, and shutting down. These events are crucial for actions of setting up, persistence, and transformation of data.
Next big thing is to understand the difference between pages, views, and controls. Even though mobile frameworks are designed with multiple pages in mind, I have come to realize that most of the apps can be made with a single page, where different views and controls will be displayed on demand.
In order to create mobile applications, we need to have a way of controlling data, UI elements with bindable properties, and intermediate layers, acting as a bridge between data and UI. We can use objects created via a singleton pattern for data management.
It will be the core objective of the app. This object will provide more than data containers, as we’ll see later. Most frameworks already have a page -> controller connection, which we can use as it is.
The most vital object for the whole app will be the singleton object. It needs to provide handles for UI element show/hide actions, history tracking list, and some sort of bindable lists of any first level API resources.
Because the number of these lists can be quite big, in complex applications, we can create new classes that will facilitate those lists. Once made, instances of that class will be instantiated inside the core object.
For API access methods, we can do similar in core objects, group them in one class and add to a singleton. Until now we pretty much have an object that is responsible for fetching data, storing it, and should be able to trigger displays of views or overlays.
Every mobile platform is pre-designed to show the initial page, whether it's called App, Main, or Home. It shares the same destiny of becoming the only page inside our app if we go back to the very act of starting up an application. On its startup, the application loads all the framework bits and dependencies.
This is where the splash screen comes to play occupying the screen, while things load. During this part, we can do setting up of things, like screen width, height, dpi, base URL, and check whether we already have API token, stored internally, for example.
No API requests or any other processor-intensive operations should be done here, as the app is already stressed by itself, trying to load fast as possible.
There are methods like “OnStartup '' or “OnCreate” in different frameworks. Any of it can be used for gathering initial device data, instead of an actual splash screen controller.
Once everything is loaded, the Application will try to load the main page. Just like the application process itself, pages have lifecycles as well. So in the constructor or any override initializer, we decide which view will be loaded first in the viewport, based on token existence or some other condition.
How do we do that?
The main page should contain at least four containers. Two containers will be dedicated to holding full-screen views, where one of them will be dedicated to hidden and other, to visible views. The same goes for the other two containers, except that they are in charge of overlays (popups, slide-ins, and others).
Overlays container should have precedence overviews container. Even though views themselves are pretty much empty shells, with a bunch of boxes, when they are not populated with data, if rendered, they will still take significant amounts of RAM and can clog UI thread.
However having them not rendered, makes a whole world of difference on that end, while still allowing the developer to reference it and its bits. In XAML, property responsible for disabling rendering of elements is “Visibility” and is completely different from property “Opacity”, in a sense that opacity is only affecting alpha visibility of elements, still rendering it and occupying resources. Any framework should have equivalents for those two.
Here we can take one of two turns. The first one and a bit easier are to add views and overlays to their respective ‘non-rendered’ containers. This ensures that all visual components get initialized. It’s a bit costly on startup, as in many cases users will not go through every view and popup.
In this case, data binding will not happen inside the UI element constructor, so only elements that are going to be created will pretty much be empty boxes, no lists or carousels.
The second path is to load elements on demand. It means that once the view is requested a separate method on the main page, will need to check for element existence and instantiate it, if necessary.
Flickers on initial screen renderings are possible with this method (usually only on low-end devices). I prefer the first method, even though it requires views and overlays, to implement hooks that can be called, in order to get data.
Once we have decided which view should be displayed, we move it from a ‘non-rendered’ container to a ‘rendered’ one. ‘’Big deal’’ in this process are two delegate methods, “ToggleView” and “ToggleOverlay”.
The difference between these two is in the behavior they produce. ToggleView is conceived to manipulate UI elements that should completely overtake viewport, so once it is executed, any view or overlay still in the ‘rendered’ container, will be moved to their ‘non-rendered’ counterparts.
ToggleOverlay is different in a way that manipulates the existence of elements, in only overlay containers. These delegate methods can be made in a way that they accept a range of arguments.
There are two usual arguments and they are: state – it is ‘’boolean value’’, telling delegate whether the targeted element should be hidden or shown; and the other is uiEelement - a string that has value like “profileView”, which tells delegate what should be targeted (can be mapped, however, not necessarily the string).
Other arguments that can be included are ‘animated’, ‘duration’, ‘lifespan’, and similar, which will later affect the way new elements get presented on screen. After the view has been moved to the rendered container, if not done in the constructor, then UI elements should subscribe to the same delegate as the main page and detect their time and load necessary data.
Every view can contain subviews that are not part of the main view alternating routine. They can have direct access to singleton data, just like their parent. Every delegate invocation should be followed by making a record in the history log, so we can navigate back if needed.
Every view should have a controller class. In each controller, there should be a method, responsible for gathering and reshaping of data, for purposes of view. We already established that API data storing and fetching will happen inside a singleton object.
The good part of it is that we can control on the first level when resources are going to be fetched again, or retrieved from the memory. Another good thing is data decoupling. While in many cases, objects received via API will already be ‘view ready’, it may happen that they need restructuring for view purposes.
For example, even if the structure itself may not be an issue, we might end up with 1000 results of something. Our view is consuming only 10 at a time.
That’s where these local data methods come in handy, allowing us to chunk and remodel data, for the view. Another benefit is, we get to keep the original data as it is.
What is important while doing any data manipulation, is that if we are changing values of objects, and those changes need to be reflected elsewhere in the app, we apply changes directly to the data object, inside singleton.
In order to achieve a single-page application style, inside mobile applications, we can create containers for visible and non-visible elements, and then alternate elements position between those two.
Empty views can be loaded (not rendered), into memory on startup and then on-demand rendered, while at the same time triggering data population. Custom history tracking implementation needs to be created, in order to store order of views the user visited and relevant data.
While this approach may be limiting for some apps, I am yet to find where it exactly fails. It relies on knowing and understanding the application lifecycle, and view controller coupling, and gives you the ability to control when dynamic data will be refreshed and when not, allowing fast transitions between views.