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.
- Yes, use view models.
- Use Dependency Injection (like Hilt) or any of the alternatives (Koin, Dagger, etc.)
- 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:
- User taps UI "view".
- Fragment forwards action to the ViewModel.
- 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).
- 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). - 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 newOrchestrator
it just received to aflow
StateFlow
orSharedFlow
(or evenLiveData
if you use that, I wouldn't since StateFlow is better most of the times). - 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 fromnot connected
toconnected
.
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.