Synopsis
Find yourself "navigating" through multiple views with an inordinate amount of setVisibilty(...)
instances in your code? Here's how to simplify that process and improve the maintainability of your code.
The problem
I inherited a project a few months back. In this project, a fragment needed to manage several views, but each of these views had too little functionality to warrant creating a separate fragment for each of them.
The original author, also making the same assertion decided to cycle through the views by controlling their visibility
parameter.
view1.visibility = View.GONE
view2.visibility = View.VISIBLE
You could find several instances of the code above in the fragment class.
Now, this approach was simple enough and worked, but there were a few issues.
Anytime a new view was added, the navigation logic needed to be updated.
Also, as the number of views increased, the code became cluttered pretty quickly because the number of
setVisibility()
instances increased.Anytime the user wanted to route back to the previous view, we first had to check which view was currently visible and based on the result, determine which view to display.
This approach, though not incorrect, made it challenging for another developer to understand the navigation flow. It became even more challenging to make simple changes like adding new views or even altering the existing flow.
eg: if the initial flow was
view1 -> view2 -> view3
and it became necessary to refactor the order to
view2 -> view1 -> view3
Such a task required changing multiple sections of the code, which was not ideal.
The solution
After spending a few hours trying to understand the navigation flow, the need to refactor became a necessity.
The first thing to do was to try to get rid of all the setVisibility()
code repetitions, hence the method below.
Avoid code repetitions
fun navigate(from: View, to: View) {
// outgoing view
from.visibility = View.GONE
// incoming view
to.visibility = View.VISIBLE
}
This was great, we could at least get rid of the repeated code by calling the navigate(...)
method above whenever navigation was necessary. But there was still the problem of navigating backward.
Since we did not want to depend on the view that is currently visible to determine the previous view, we needed a way to keep track of all our view navigations.
Tracking navigation history
Let's create a data class that will hold the current view and the next.
We then employ the use of a Stack()
to preserve the navigation history and update the previous navigate(from: View, to: View)
method to make use of the ViewNavigation
class.
(This process is similar to how a backStack is used for Fragments)
Now let's see how we make use of our newly created navigationStack
to navigate forward and handle back presses.
Each time we call the navigateViews(...)
method we create a ViewNavigation
object and push it onto the stack.
Handling back navigations
When attempting to navigate backward, we pop the last item in the stack, as that item represents our most recent navigation.
As indicated in the comments, we invert the from
and to
params when creating a ViewNavigation
object that will be used for navigating in the opposite/reverse direction.
The back()
method returns true
if back navigation was successful and false
if there are no more views left in the stack. When that occurs, we hand over control back to the fragment/activity.
Conclusion
That's it! We're done. Now, whenever we need to display a particular view, all we need to do is call the CustomNavigator.navigateViews(from: View, to: View)
method.
We no longer need to manually check which view is currently displayed before navigating.
And when new views are added, the navigation logic will not need to be updated!
Check out the full implementation here!
Top comments (0)