mercredi 12 juin 2019

Sharing display state between views in MVVM without relying on singletons

My app has several long processes that are user-initiated by different fragments and I'd like to display a progress dialog while they are being accomplished. I'd like this progress dialog to be displayed across several fragments so that bottom navigation is still enabled, so that the user still has a sense of agency, but they can't take any actually harmful actions otherwise. Most of my "main" fragments are blocked in this way, but others such as the settings and help fragments don't need to be.

My current solution is less than ideal. Actions are user-initiated in the fragment, but the activity becomes responsible for actually completing them, so that it can overlay a progress dialog over every fragment that it needs to. I'd much rather have the fragments be responsible for their own tasks. The obvious separation of concerns is becoming pretty essential, as the size of the activity VM has become massive, and too much of the business logic is in that class (and is then delegated to the model appropriately).

e.g.

class MyFragment : Fragment() {
    // MyActivity will implement this interface
    interface NetworkProcess {
        fun start()
    }
    // start() is called on a button click or something similar
}

class MyActivity : AppCompatActivity(), MyFragment.NetworkProcess {
    override fun onCreate(savedInstanceBundle: Bundle?) {
        // Observe state from VM layer
        // Observer updates progress dialog
    }
    override fun start() {
        // Pass action to VM layer
     }
}

One solution I have tried is to have the interface only used for display state, but then if the fragment is navigated away from it is unable to call the interface and continue updating the dialog.

e.g.

class MyFragment : Fragment() {
    // MyActivity will implement this interface
    interface NetworkProcessDialog {
        fun update(text: Int)
        fun stop()
    }
    override fun onActivityCreated() {
        // Observe state from viewmodel
        // Set listener on a button send action to viewmodel to start process
    }
}

class MyActivity : AppCompatActivity(), MyFragment.NetworkProcessDialog {
    override fun updateDialog(text: Int) {
        // Make dialog visible if not and show update
     }
    override fun stopDialog() {
        // Make dialog invisible
    }
}

Another solution I've thought of is to push the view state into the model layer. It seems though that this would necessitate a singleton (and a bunch concurrency protection work) so that the viewmodels can't start overwriting the state as it's in the process of accomplishing one of these long running tasks. This is currently avoided automatically since there is only ever one activity VM handling all the work.

So, is it possible to have this kind of separation of concerns without relying on singletons in the model layer?

Aucun commentaire:

Enregistrer un commentaire