We always strive to write code which is compliant with basic coding standards like SOLID, SLAP(Single Level Abstraction Principle) etc. We know these principles are fundamental and they take care of scalability, maintainability and many other factors for the most part.
This article is just another attempt to make things easier when it comes to handling the UI of our application, so we start by first looking at a fundamental use case.
The Problem Statement:
The fact that our UI can request data, we develop views which would show its states. If we take a look at a basic operation here are some of the basic states that we can land into:
- Fetching data
The state where you would be requesting data from your repo and the data is being fetched irrespective.
- Data fetched successfully
The state where you get the data you requested and you update your UI accordingly.
- Data fetch failed
Getting issues while fetching data is not something new, we always keep a UI which essentially gives a retry mechanism which helps users get back on track.
There are some of the basic states we might incur when doing the most basic operation in android, however, things might get complicated when we have multiple data fetching in a single screen with different repositories.
It doesn't stop here, once you fetch the data-set that doesn’t mean we just take the data and update the UI. A lot of times the state of the data would itself have multiple states associated with it. Sounds confusing.
Consider this example:
Considering we have an app that manages different types of skilled experts, now depending on the skill set, we take different inputs from the user and the same would down-steam in respective flows which means every piece of UI has different states(info). Consider these not at a screen level where you can put them into fragments but in a widget context where you have multiple sections tiled in a View.
So now your data would tell you the types of expert it is and from that data, you decide which UI to show up. Now if you picture this in code it might look something like this:
What’s wrong with this approach:
Here’s a list of some of the issues that we can find by looking at the code snippet above:
- Open for modifications:
A problem that you can tell just by looking at the code is it's open for modifications which simply means every time you add a new expert in the system. You’ll have to come back to this file and handle the case which incurs modifications in an existing file which one should always try to avoid.
- Poor Encapsulation:
If you look closely you will find the data being used here is open for any state to access. The type check is happening at the UI layer as the views are accessible at the same level. Would have been great if a state could have been able to access only the data it deals with.
- Open View access:
When we define functions in the UI layer which are supposed to manipulate only a section of view can misuse and can modify the states of other widgets which not only makes it difficult to debug view issues but also makes your view venerable to being modified anywhere in the UI layer.
- Limitless LOC:
When our code is open for modifications then it's very hard to limit the lines of code in a file which ideally shouldn't be more than 500 Lines of Code. Consider the statement above, as experts keep adding to the system. Your LOC will keep increasing.
- Private functions chaos:
when updating different parts of UI, we would for sure have multiple private functions associated with the primary function and this means you’ll be having multiple private functions for multiple states.
What we can achieve:
Now, this is soothing and here’s how we can do this:
As clean as it looks, it is as equally easy to implement.
How to make it work:
So we’ll use just the basics of Polymorphism to achieve this by defining an abstract class here’s how we do it:
1. Define an interface for states:
we define an abstract class so that we can connect the rest of the states with a widget. Here we would reset the view of our widget to a default state from where our states can take charge.
2. Defining states:
You define the finite set of states that the UI can withhold and you encapsulate the logic there. This is a place where you add all your private functions that reside now properly encapsulated.
3. Pushing States from Repository:
Now Inside your repository, depending upon the use case we would push the State and encapsulate data inside the same. This way States define the data they require and that’s how we encapsulate their data dealing.
4: We just listen on the UI layer:
At last, we just attach the observer and it takes care of routing the state to its respective implementations. That’s It!
By doing all this you have not only made your code compliant to basic coding standards set by SOLID but you would enjoy the mechanics when adding more states to our UI.