A well-architected Android app depends on a publisher or subscriber pattern. In this video, learn how to publish data from a repository using LiveData objects.
- [Instructor] So far my app is retrieving data with conventional function calls. But my ViewModel and everything else in the app will end up tightly coupled with the data source. It'll have to know about particular functions in the repository that are being executed and I will even want to hide those details from the rest of the app. A well architected Android app instead depends on a publisher-subscriber pattern. In this pattern the repository acquires the data, but instead of returning the data from a function, it publishes it using a component called LiveData. Any other component in the application can then subscribe to handle changes to the data using a pattern called an observer. To implement this I'll create a new variable that's a property of the repository. I'll call it monsterData and I'll create it and it's an instance of the class MutableLiveData. Whenever you declare a MutableLiveData object you always need to set its type using a generic declaration, and this will be a List of Monster objects. This is a constructor call so I'll add the parentheses at the end. Next, I'm going to change how this function is called. It's no longer going to be called from the rest of the application so it won't be able to get its context when the function is called here. Instead I'm going to add an argument to the MonsterRepository constructor. It'll be an Application object, and it'll reference the application context. And then down here, where I'm calling gettextFromAssets, I'll use that app reference instead. Notice I'm not simply passing in a context, I'm being very specific. I'm saying I want an application reference. That's because I want to prevent myself from passing in an activity reference. You never want to hold on to references to activities. That creates a resource leak. The application context will always be around as long as the application is running in any form. So holding on to that reference is okay. Now I'll change this function. It's no longer going to return data so I'll get rid of that declaration. And here, instead of a return statement, I'll say monsterData.value equals, and then I'll use the same expression, that either returns actual data or an empty list. So now I'm publishing the data to the rest of the application through the LiveData object and it's up to every other part of the application to listen for changes. I'll go back to my ViewModel. In the ViewModel I'll change how I'm instantiating the MonsterRepository object by passing in the app reference. Then I'll create a new variable that I'll call monsterData here and I'll get its value from dataRepo.monsterData. I'm simply passing this through, so that the rest of the application can listen for changes to the data. Then I'll go to my MainFragment. In the MainFragment I have this reference to the ViewModel. Now I'm going to move this around a bit. I'm going to take this ViewModel initialization and move it into the onCreateView function. I'll get rid of all this code, I don't need that anymore. And then here I'll call viewModel.monsterData.observe. The observe function takes two arguments, a lifecycle owner and an observer. You can pass in the current fragment as the lifecycle owner. That will notify the ViewModel when changes happen. And then I need an Observer object. Within the Observer object, I'll receive the data as a list of Monster objects. So I'll go back to my ViewModel. I'll delete this line of code because I'm no longer explicitly calling that function. I'll cut and paste this code. I'll get rid of the init block, I don't need that anymore. And then I'll paste that code into place right here. And instead of referencing monsterData by its name, I'll reference it as it. That is, this is the value that was passed in when the observer class received changes to the data. Finally I'll go back to my repository, I'll once again create an init block, and I'll simply call getMonsterData, so that I'm retrieving the data as the repository is created. I'll go to my Logcat window and clear it. And then I'll run the app again. And there's the data being returned but this time being handled in the user interface, the MainFragment. To see this in the actual application I'll change the code like this. Instead of logging it, I'll create a StringBuilder object. I'll move that above the for loop, and then within the for loop I'll append the monsterName and a line feed. At the end of the for loop I want to display this text in the actual application. I'll go to my layout directory, I'll look at the text here and I'll see that I have a text view with an id of message. So I'll go back to the fragment, and I'll say message.text equals monsterNames, and then I'll run the application. And when the application shows up it shows all of the monster names. I'll come back here and clean up my code a little bit. I no longer need the ActivityCreated function. I no longer need the newInstance right here. And I can clean up all of my imports. My application is now using the recommended architecture. Data is acquired from the repository, and exposed through a LiveData object. The ViewModel is simply passing that LiveData object back to the user interface. And the fragment, that is the user interface, is only responsible for managing the presentation.
- Modeling an entity
- Reading files from resources and assets
- Parsing and mapping JSON data
- Getting data from a web service with Retrofit
- Creating a RecyclerView to display data
- Displaying images dynamically with Glide
- Publishing and data values with LiveData objects
- Displaying details with data binding
- Reading and writing files
- Managing SQLite databases with Room
- Persisting data in shared preferences