Too many instances of View.setVisibility(...)?
Tags: kotlin, views, android
Published: 2021-04-26 11:16:09

### 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. 

```kotlin
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.
> 1. Anytime a new view was added, the navigation logic needed to be updated.

> 2. Also, as the number of views increased, the code became cluttered pretty quickly because the number of `setVisibility()` instances increased.

> 3. 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
```kotlin
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.

{% gist https://gist.github.com/WilderMinds/f613a218433528e0ad549840fd795af1 %}


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_)
{% gist https://gist.github.com/WilderMinds/52e7e16694632561e2ccfd16100a2b26 %}


Now let's see how we make use of our newly created `navigationStack` to navigate forward and handle back presses.
{% gist https://gist.github.com/WilderMinds/e8263ca527ce2ed5e59fea735fa16845 %}


Each time we call the `navigateViews(...)` method we create a `ViewNavigation` object and push it onto the stack.


###### Handling back navigations
{% gist https://gist.github.com/WilderMinds/54cd3298466faebc1ced4fcea7930569 %}

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.
{% gist https://gist.github.com/WilderMinds/52ab7f4c7d7aa0b28a8438f1d7b47e45 %}



### 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](https://github.com/WilderMinds/ViewNavigation)!