Android ViewModel: How to get a result from the Activity (best practice)? - android

I have the following situation:
The user opens a dialog in which the user enters information
The user closes the dialog and returns to the default UI
The data is send to the backend
Now I wonder how to implement this in a good way. At first, I think the Activity calls a method in the ViewModel to trigger an event. Secondly, the ViewModel updates an LiveData-object to trigger an observer in the Activity which opens the dialog. But after that I don't know how to implement the rest in a "best practice"-way. My current implementation is that, when the user closes the dialog, the Activity calls a "finish"-method in the ViewModel and hands over the data from the dialog as arguments. After that the ViewModel updates the LiveData-object and the event is over.

You don't specifically need to have a flow Activity -> ViewModel -> Activity, when you're just about opening a dialog. It would make sense if you have to get some info (for example price) from your back-end side and include it into your dialog description. If it's just a simple UI stuff like "show-me-a-dialog", then it's fine to leave it on your UI layer only.
And when the dialog is closed, you'll just pass needed arguments to VM, make your back-end request, and in this case it's logical to emit some event to your Live Data and it's a common practice.
Android has a bunch of really great articles and they were added no so long ago, take a look here (picture was also taken from the article). They have clear and simple explanation about all UI-VM-Data/Domain communication and how to do that it in the "right way".

Related

Observe runs multiple times on button click for the second time

I have this code in an activity SignInActivity:
signInButton.setOnClickListener{
val query: HashMap<String, String> = HashMap()
query["email"] = signInEmail.text.toString()
query["password"] = signInPassword.text.toString()
signInViewModel.getAuthToken(query)
signInViewModel.signInResponse.observe(this, {
response-> when(response){
is NetworkResult.Success ->{
response.data?.let { Toast.makeText(this, it.access, Toast.LENGTH_SHORT).show()}
}
is NetworkResult.Error ->{
Toast.makeText(this, response.message.toString(), Toast.LENGTH_SHORT).show()
}
is NetworkResult.Loading -> {
}
}
})
}
Let's suppose in the first try I wrote my password wrong and it only runs once, but then after that if I click it again it runs multiple time by creating multiple toasts in this example.
Like #gpunto says, you're adding a new Observer every click, so they're stacking up and each one fires when the LiveData updates.
But really, the observer doesn't have anything to do with the actual click anyway, it just receives updates to signInResponse and displays a thing. The click just calls getAuthToken with the current query. If doing that happens to cause a signInResponse update, then you have everything wired up to react to that event. But the Activity doesn't need to know how all that stuff works, or be written so one thing follows another.
That's a reactive pattern, where your UI is really just sending events (like getAuthToken when there's a click) and then reacting to other events so it can display them. By separating these things, you get a simple system that Just Works, and can react to updates no matter what caused them (e.g. a click, or restoring state) without having to write code to handle each case.
That said, this is a slightly tricky case because you have an event you want to consume. If you just set up that observer on signInResponse, it will fire every time you get a value for that LiveData. And that includes when the Activity is recreated (e.g. on rotation), observes the LiveData, and gets the current (last-set) value. Basically, if you show a Toast, the same Toast will appear every time the Activity is recreated. That would be fine for setting the current value on a TextView, but it's bad for a popup that should only appear once.
This is the current official recommendation for handling this situation. They're creating a UI state, which basically holds everything that needs to be displayed, including any popup messages (which acts like a queue, which is useful!). When the UI displays a message, it basically tells the ViewModel it's done so, and that handles removing the message from the state.
You could just implement this your own way, even if it's something simple like a clearResponse() function in your VM that clears the current value when you've seen it. It really depends on your app and what state you need to maintain. Here's some other examples from the Android devs - but like it says at the top, this advice is deprecated following the recommendations I linked earlier

Passing activity result back to grandparent activity (not parent)

I have a situation in which I have 3 activities.
ResultsActivity
SimpleSearchActivity
ComplexSearchActivity
The flow works as follows: The user can choose to search from the ResultsActivity which will launch the SimpleSearchActivity. From there the user can perform a simple search. The result is pass back to the Results activity.
However from the SimpleSearchActivity the user can choose to do a complex search which will finish the SimpleSearchActivity and launch the ComplexSearchActivity. From there I want to pass the result back to the ResultsActivity. I am not sure how to do this since this 3rd activity was not launched from the first activity, but the second.
Options I have considered:
In complex search case go back to ResultsActivity and launch the ComplexSearchActivity from there. Not sure I really want this as I don't want the SimpleSearch to close go back to the Results then immediately launch the Complex. I am worried this will 'flash' the ResultsActivity before launching the ComplexSearchActivity.
In complex search case, from SimpleSearch launch ComplexSearch with startActivityForResult(...), on complex finish the simple search can grab the result, then pass that back to the Results activity. I am not sure this chaining will work. Even if it does I am worried that on complex finish the SimpleSearchActivity will 'flash' just to pass the results back.
Store the search results in a static variable somewhere and on ResultsActivity launch just check for that static variable that might have been set by either search activity. Yuck!
I have also thought of only having one search activity and doing a show/hide on certain fields (simple vs complex). However I launch the simple search as a dialog activity (in tablet case) and complex search as full screen activity. So I cannot really use the same search activity.
Ideas? Has anyone tried something like this before?
There are a couple ways you can achieve this:
Option 1: You can choose not to close SimpleSearchActivity when you launch ComplexSearchActivity. Instead, have ComplexSearchActivity send its result back to SimpleSearchActivity, and from there have SimpleSearchActivity pass that result to ResultsActivity.
Update: This will, at least in my experience, have no UI "flash"; it should appear seamless.
Option 2: Use FLAG_ACTIVITY_FORWARD_RESULT, which exists for exactly this purpose.
Have you think about the approach the abstract the data layer to be shared across all activities rather than to have only certain activity to hold the data?

Making data accessible to fragments

I am having an app which is retrieving data in the main activity and sending an event to all fragments as soon as it is available. So for the first start it looks like this:
App starts (fragments are initialising in the background) -> feed download -> notification sent to fragments -> fragments initialise UI
Everything's fine so far. BUT, what if I am resuming the app. The data will be still cached, so i will send the event immediately on app resume, and therefore it can happen that my fragments are not even ready for receiving the event -> no fragment UI update!
Or the event is triggered and received in the fragment, but the fragment is not ready for the UI update, cause it still hasn't inflated the layout -> NullpointerException
Or the fragment receives the event, but is not attached to the activity anymore -> another Exception.
There are ways to deal with single issues, but overall it is complicating the architecture a lot.
Somehow I tried a lot of things (playing around with Otto bus) but somehow I can't find any architecture which is working for making a central datasource available to all activities and fragments in the app.
How do you supply your fragments with data if you don't want to use bundles?
First of all a Fragment should be independent from other parts of an app. Moreover it shouldn't know parent activity: getActivity method should return just an Activity which could be casted to some interface.
an Activity shouldn't be a "data downloader". Basically activity is a View which receives various system and user events and displays particular state. For instance when the system creates activity it calls method 'onCreate' where activity should create/arrange fragments and views.
there is should be some manager or controller(call it as you wish) which knows where and how to get data for views. For instance if there is no internet connection it loads data from local database otherwise it makes network request.
So roughly speaking flow should look like this:
fragment(or activity) has reference to the DataManager. The fragment subscribes on FeedDataEvent in the onResume method. When fragment wants(onResume method for example) to show some data to the user it calls DataManager.loadFeed() and displays to the user "loading..."
DataManager checks if there is Task which is loading data from network. If there is no such fast it starts it.
When data is downloaded DataManager emits FeedDataEvent.
If the fragment is still visible it receives that event and shows data. If the user left the app fragment unsubscribed(in the onStop method) from FeedEventData and will not receive that event.
There is subtle thing with requests caching(making network request on every onResume is not very good idea) but it depends on particular app.
PS Almost all this things are implemented in RoboSpice and some other libraries.

Updating multiple instances of the same dialog fragment

I have an app where multiple emergency messages can be received. When each message is received it needs to open a dialog with the emergency details. If more than one emergency is received it will stack the dialogs so that when the most recent emergency message is dealt with and that particular dialog closes, the previous most recent and un-handled emergency should appear, as if they are stacked.
Also, the details for each emergency are updated every x seconds from a service and these changes need to make their way to the correct dialog instance so that, when on screen the dialog is up to date.
I had this working in, I think, an un-efficient way. I was storing the instance of each dialog fragment object in a list and updating that instance with new details and then opening it. This meant that if 50 emergencies were spammed I was storing 50 dialogs. Not great for memory.
Also, this method didn't work well with orientation, where the object is destroyed and re-built, it was taken out of my list and the details reverted to the original details which were stored in it's intent.
I am looking for a way to do this that is as efficient as possible and I wanted to ask your collective Android brain for suggestions.
How would I efficiently manage multiple instances of the same dialog class to get the behaviour I require?
What i would do, is to just hold EmergencyMessage data object stack in my Activity. When stack is not empty, i would show new DialogFragment, and pass it EmergencyMessage from top of the stack, using Fragment.setArguments.
In my DialogFragment, i would use Fragment.getArguments to get EmergencyMessage object and display message. When user deals with message, notify your activity (for example like this) and close dialog. Then, if you still have unhandled EmergencyMessages in your stack, just repeat the process.
This way you always have single DialogFragment instance active at any given time.
Now, regarding updating the EmergencyMessage contents if DialogFragment is already showing this message, you can do it from activity like this:
When you create dialog, pass it tag "EmergencyMessageDialog" so you can retreive dialog later and interact with it.
MyDialogFragment dialog = (MyDialogFragment)
getFragmentManager().findFragmentByTag("EmergencyMessageDialog");
dialog.updateEmergencyMessage(newEmergencyMessage);
If you receive update for EmergencyMessage that hasnt been shown yet, you can just update your EmergencyMessage object in your stack in activity, so that would be easy.

Android: When to use Dialog vs. Activity

When is it preferable to use a Dialog box as opposed to an Activity in Android? From my understanding, anything you can do with a Dialog box you can also accomplish with an Activity. Are there any tasks that can only be accomplished by an Activity or a Dialog?
Is what you're doing worth a new Activity? Do you need to be able to start it through an intent? Do you really need to create a new Java class for it?
If it's a straightforward dialog that displays a text and has simple hooks for positive/negative/dismissal functions, definitely use a dialog.
If you have something complex, you may want to go for a full-blown activity.
Well why exactly would you want to start a new activity just to ask the user "Are you sure? Y/N"? Dialogs generally run on top of the activity, and are usually smaller activities or notifications for the user. They also usually have something to do with the process of the app running. It helps make things simpler to open a dialog to prompt the user on top of your activity, than to start a new activity atop your current activity.
I went for Activities when I needed an user interaction that needs backstack, navigation, lifecycle and callable features.. else with dialogs. Being from the WebApp world I ask whether I would have needed a new server page or a pop window for an interaction and the decision in Andoird world becomes easier!
If newServer page then mostly Activity
elseIf popUpWindow then dialog
I created my android application in one fragment with nested alert dialog, so far my application still running well in handle those nested dialog, and I think dialog is less consuming memory than an activity

Categories

Resources