Home > Software engineering >  Android - Best practice to pass objects between fragments
Android - Best practice to pass objects between fragments

Time:01-23

My scenario is like this: I have a one activity architecture of my app, and I have multiple fragments, which I navigate to using the NavController object.

In my MainActivity I create an object called Orchestrator which I want to create just once, and use all over the app and fragments. The Orchestrator also holds the context of the MainActivity.

I currently pass the object between the app by createing an interface called IShared look like this:

interface IShared {
    Orchestrator getOrchestrator();
}

The MainActivity implements this interface so it looks something like this:

public activity MainActivity extends AppCompatActivity implements IShared {

    private Orchestrator orchestrator;
    
    @Override
    public onCreate() {
        ...
        orchestrator = new Orchestrator(requireContext());
        ...
    }
    
    @override
    public Orchestrator getOrchestrator() {
        return orchestrator;
    }

}

and I get the Orchestrator object in the fragment through the onAttach method:

private Orchestrator orchestrator;

@Override
public void onAttach(Context context) {
    orchestrator = ((Orchestrator1) context).getOrchestrator()
}

but my problem is that the onAttach method is deprecated, so I guess this is not the best practice of doing this.

What should I do then? Should I use a ViewModel that will hold the orchestrator even though it is not a UI object? it is an object which holds a lot of data that I need to pass between fragments and some of the fragments also changes the data of it.

CodePudding user response:

Use the recommended practices.

Various ideas.

  1. Yes, use view models.
  2. Use Dependency Injection (like Hilt) or any of the alternatives (Koin, Dagger, etc.)
  3. I don't know what your orchestrator does, but I don't think it's the Activity's responsibility to create such an important object that you want shared across instances of UI. Feels like there's got to be a Factory/Repository where this object comes from, and that each viewModel can pick from.

Do not fight the Framework, and don't couple all your fragments with an interface like that. Either inject it directly in the fragments via DependencyInjection, or observe the object through a viewmodel (shared?) and or individual viewModels for each Fragment/Screen. Or Both, you can have a shared viewmodel (provided by the activity) in addition to individual view models for each fragment.

even though it is not a UI object?

If it's not a UI object, then why is it in the UI to begin with?

UPDATE

it's in the UI because some buttons in the UI, changes some of the fields inside the object. for example pressing the "connect" button in the UI, changes the "connected" boolean variable in the orchestrator to true. How can I create this object and add a context to if, if not from the MainActivity?

If pressing a button changes the state of the object, then the correct flow would be:

  1. User taps UI "view".
  2. Fragment forwards action to the ViewModel.
  3. ViewModel decides what happens (in this case, the connected button was pressed, so it informs the UseCase/Interactor that knows what to do when the connect button is pressed).
  4. UseCase/Interactor does what it needs. In this case, updates or returns a new Orchestrator with the state changed (connected = true). If you're using coroutines, this is potentially a suspend function as well (probably not to just update a boolean... but if you needed to do more than that, then probably yes).
  5. ViewModel, which called val newOrch = connectedUseCase.onConnected() (for e.g. remember this is pseudo-code, there are multiple ways to do this), now emits the new Orchestrator it just received to a flow StateFlow or SharedFlow (or even LiveData if you use that, I wouldn't since StateFlow is better most of the times).
  6. Fragment or Activity that initiated this, was observing its state from the ViewModel all the time and will collect the new State and react to it (say, for e.g. by changing a text from not connected to connected.

Now you'd argue this is a lot of overhead for a "simple boolean change" and I'd somewhat agree that indeed it is, but such is the complexity of a modern, decoupled application.

The benefits are mid-long term, as you'd now no longer need to worry about this operation (the Usecase can be reused) and the Activity/Fragment no longer really know much (nor maintain) their "common Orchestrator".

All these concepts are better explained and expanded in the Guide to Android Architecture official website.

As for the final bit of your question:

How can I create this object and add a context to if, if not from the MainActivity?

If this object must exist from the beginning of your app, then your ViewModel#init() (for your activity) could call a UseCase or create it itself. You say it's not a UI object, but it has UI STATE in it, so someone must create it, set the initial state, and hold a reference to it. Either have an Orchestrator "Repository" or "factory" or "OrchestratorInitUseCase" that creates it for you.

You do not need (and should not) have a Context Stored in any of these things. You only need the Context to perform Android Framework operations (theme, navigation, etc.) and these can happen in the Fragment/Activity that triggers the action/event once the ViewModel they are observing emits the corresponding value(s). There's no need to send the context anywhere.

  • Related