Is this a good approach or I've just found a nasty workaround?
I'm using MediatorLiveData class because seems useful to update the source of a LiveData object.
I mean, the majority of tutorials that I've found on internet just use Livedata or MutableLivedata without a dynamic source, in example:
fun search(/*no input params*/): Call<List<Person>>
But in my case, I have the following web service that performs a search by name:
interface APIServidor {
#GET("search")
fun search(#Query("name") name: String): Call<List<Person>>
}
public class PeopleRepository {
public LiveData<List<Person>> search(String name){
final MutableLiveData<List<Person>> apiResponse = new MutableLiveData<>();
Call<List<Person>> call = RetrofitService.Companion.getInstance().getApiServer().search(name);
call.enqueue(new Callback<List<Person>>() {
#Override
public void onResponse(#NonNull Call<List<Person>> call, #NonNull Response<List<Person>> response) {
if (response.isSuccessful()) {
apiResponse.postValue(response.body());
}
}
#Override
public void onFailure(#NonNull Call<List<Person>> call, #NonNull Throwable t) {
apiResponse.postValue(null);
}
});
return apiResponse;
}
}
Then in the viewmodel class I'm adding source per new request.
public class SearchViewModel extends ViewModel {
private MediatorLiveData<List<Person>> mApiResponse;
private PeopleRepository mApiRepo;
public SearchViewModel() {
mApiResponse = new MediatorLiveData<>();
mApiRepo = new PeopleRepository();
}
public LiveData<List<Person>> getPlayers() {
return mApiResponse;
}
public void performSearch(String name){
mApiResponse.addSource(mApiRepo.search(name), new Observer<List<Person>>() {
#Override
public void onChanged(List<Person> apiResponse) {
mApiResponse.setValue(apiResponse);
}
});
}
}
Activity
bt_search.setOnClickListener {
val player_name = et_player.text.toString()
viewModel.performSearch(player_name)
}
Project scope
I'm in a personal project
Goals
Use MVVM + Live data + Repository pattern
Problem
I've only found tutorials with a simple approach: observe a LiveData object that access to a repository object and fetch data only once.
In example: Fetch all people (select * from people) from web service.
My case: Fetch people that mach a name (select * from people where name=?) from web service.
https://medium.com/#elye.project/kotlin-and-retrofit-2-tutorial-with-working-codes-333a4422a890
https://medium.com/#sriramr083/error-handling-in-retrofit2-in-mvvm-repository-pattern-a9c13c8f3995
Doubts
Is a good idea use MediatorLiveData class to merge all requests took from user input?
Should I use MutableLiveData and change the repository class and use a custom clousure?
Is there a better approach?
I was using this pattern with MediatorLiveData as well, but it forms an issue.
From the user perspective it seems to function just fine, but one problem here is that every time you call performSearch() the repository creates a new LiveData object which is additionally added to MediatorLiveData via addSource().
An idea might be to have the repository create the MutableLiveData object only once and on consecutive call just update it's value. So f.e. MutableLiveData<List<Person>> apiResponse; would be a non initialized private field that gets initialized in the search() method.
Eg. if (apiResponse == null) apiResponse = new MutableLiveData();
Related
I'm trying to learn MVVM to make my app's architecture more clean. But I'm having a hard time grasping how to create a "Domain" layer for my app.
Currently this is how the structure of my project is looking:
My View is the activity. My ViewModel has a public method that the activity can call. Once the method in the ViewModel is called, it calls a method in my Repository class which performs a network call, which then returns the data back to the ViewModel. I then update the LiveData in the ViewModel so the Activity's UI is updated.
This is where I'm confused on how to add a Domain layer to the structure. I've read a lot of Stackoverflow answers and blogs about the Domain layer and they mostly all tell you to remove all the business logic from the ViewModel and make a pure Java/Kotlin class.
So instead of
View --> ViewModel --> Repository
I would be communicating from the ViewModel to the Domain class and the Domain class would communicate with the Repository?
View --> ViewModel --> Domain --> Repository
I'm using RxJava to make the call from my ViewModel to the Repository class.
#HiltViewModel
public class PostViewModel extends ViewModel {
private static final String TAG = "PostViewModel";
private final List<Post> listPosts = new ArrayList<>();
private final MutableLiveData<List<Post>> getPostsLiveData = new MutableLiveData<>();
private final MutableLiveData<Boolean> centerProgressLiveData = new MutableLiveData<>();
private final MainRepository repository;
#Inject
public PostViewModel(MainRepository repository) {
this.repository = repository;
getSubredditPosts();
}
public void getSubredditPosts() {
repository.getSubredditPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Response>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
centerProgressLiveData.setValue(true);
}
#Override
public void onNext(#NonNull Response response) {
Log.d(TAG, "onNext: Query called");
centerProgressLiveData.setValue(false);
listPosts.clear();
listPosts.addAll(response.getData().getChildren());
getPostsLiveData.setValue(listPosts);
}
#Override
public void onError(#NonNull Throwable e) {
Log.e(TAG, "onError: getPosts", e);
centerProgressLiveData.setValue(false);
}
#Override
public void onComplete() {
}
});
}
public class MainRepository {
private final MainService service;
#Inject
public MainRepository(MainService service) {
this.service = service;
}
public Observable<Response> getSubredditPosts() {
return service.getSubredditPosts();
}
}
Could someone please give me an example of how I could do it? I'm quite lost here
I had a hard time while trying to figure out the domain layer.
The most common example of it is the use case.
Your viewmodel won't communicate directly to the repository. As you said, you need viewmodel 》domain 》repository.
You may think of a usecase as a abstraction for every repository method.
Let's say you have a Movies Repository where you call a method for a movie list, another method for movie details and a third method for related movies.
You'll have a usecase for every single method.
What's the purpose of it?
Let's say you have a DetailActivity that communicate with a Detail Viewmodel. Your viewmodel doesn't need to know all the repository (what's the purpose of calling a movie list method on you Detail screen?). So, all your DetailViewModel will know is "Detail Usecase " (that calls the Detail method in repository).
Google has updated the architecture documentation few hours ago, take a look!
https://android-developers.googleblog.com/2021/12/rebuilding-our-guide-to-app-architecture.html?m=1&s=09
PS: Usecase is not a special android class, you do not need to inherent any behavior (as fragment, activity, viewmodel...) it's a normal class that will receive the repository as parameter.
You'll have something like:
Viewmodel:
function createPost(post Post){
createUseCase.create(post)
}
UseCase
function createPost(post Post): Response {
return repository.create(post)
}
I spent quite a bit of time trying to learn how to add a domain layer using RxJava by reading a lot of blogs and Stackoverflow answers, but all of them were missing the conversion of the response from the api call to what you'd like to display on screen (For example if the back end returns a username dave123 and you'd like to display by dave123).
I finally figured it out and the secret sauce was to use a RxJava .map() operator inside the UseCase class. I also decided to keep the RxJava call inside my ViewModel.
So in my Repository class I have a method that calls the Api and returns a type of Single<Response>. This is the raw json data the Api returns.
public class MainRepository {
private final MainService service;
private final PostDao postDao;
#Inject
public MainRepository(MainService service, PostDao postDao) {
this.service = service;
this.postDao = postDao;
}
public Single<Response> getResponse() {
return service.getSubredditPosts();
}
}
Inside my GetPostsUseCase class, I'm call the getResponse() method from the MainRepository and altering the Response by performing business logic on it (the stuff I want to display on the UI. In this case I add the String "by " to the username)
And the secret or the part I had alot of trouble understanding/figuring out how to do was converting the Type inside the Single<>. I used the .map() operator to change the return type and filter the Response to a List<Post>
public class GetPostsUseCase {
private final MainRepository mainRepository;
#Inject
public GetPostsUseCase(MainRepository mainRepository) {
this.mainRepository = mainRepository;
}
public Single<List<Post>> getSubredditPosts(){
return mainRepository.getResponse().map(response ->
getPostsFromResponse(response.getData().getChildren())
);
}
private List<Post> getPostsFromResponse(List<Child> listChildren) {
List<Post> listPosts = new ArrayList<>();
for (Child child : listChildren) {
Post post = child.getPost();
post.setCreatedBy("by " + post.getUsername());
listPosts.add(post);
}
return listPosts;
}
}
And this is how my ViewModel looks like
public class PostViewModel extends ViewModel {
private static final String TAG = "PostViewModel";
private final List<Post> listPosts = new ArrayList<>();
private final MutableLiveData<List<Post>> getPostsLiveData = new MutableLiveData<>();
private final MutableLiveData<Boolean> centerProgressLiveData = new MutableLiveData<>();
private final GetPostsUseCase getPostsUseCase;
#Inject
public PostViewModel(GetPostsUseCase getPostsUseCase) {
this.getPostsUseCase = getPostsUseCase;
getSubredditPosts();
}
public void getSubredditPosts() {
getPostsUseCase.getSubredditPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<List<Post>>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
centerProgressLiveData.setValue(true);
}
#Override
public void onSuccess(#NonNull List<Post> list) {
Log.d(TAG, "onNext: Query called");
centerProgressLiveData.setValue(false);
listPosts.clear();
listPosts.addAll(list);
getPostsLiveData.setValue(listPosts);
}
#Override
public void onError(#NonNull Throwable e) {
centerProgressLiveData.setValue(false);
}
});
}
I couldn't find any blogposts or answers that had an example like this. Hopefully this helps anyone out there who is struggling to learn how to implement clean architecture with MVVM, Hilt, RXJava and a Domain layer.
If I did do something incorrectly or not considered clean architecture please let me know.
I want to do GET request with Retrofit, but I don't know on which layer of MVP pattern I need to do it. As I know, Model sends all data to Presenter, and then, Presenter shows data on View. So, I thought that the best place is Model. But how Presenter will know that Model fetched all data already and ready to pass it to Presenter? For this, I think I need to use interface that notifies Presenter when Model has finished loading data. But googling what is the best way, I saw that developers use something like Repositories and Managers. But I couldn't figure out the role of each of them. So, how to solve the problem? What is the best place to create HTTP requests in MVP pattern? If it is Model, what is the best way to send all data to Presenter?
What you need is a callback structure from you model to the presenter. What I normally use and recommend is to use RxJava, retrofit2 already has an option for returning an Observable object which makes everything much easier.
Let's say you have an endpoint like this one, this is a retrofit response that returns an observable:
#Headers({"Content-Type: application/json", "Accept: application/json"})
#GET("/api/v1/banners")
Observable<Response<GetBannersResponse>> getBanners(
#Header("Authorization") String auth_token);
The GetBannersResponse class is just a POJO to encapsulate my json response:
public class GetBannersResponse {
List<Banner> banners;
public List<Banner> getBanners() {
return banners;
}
public void setBanners(List<Banner> banners) {
this.banners = banners;
}
}
My Model (interactor) class I like to call DataHandler looks like this:
public class MyDataHandler implements MyDataHandlerContract.DataHandler {
private RetrofitAPI theCloud;
private PreferencesUtil prefs;
#Inject
public CatalogDataHandler(TaskrAPIConfig theCloud, PreferencesUtil prefs) {
this.theCloud = theCloud;
this.prefs = prefs;
}
#Override
public Observable<Response<GetBannersResponse>> getBanners() {
return theCloud.getApiService().getBanners(prefs.getTokenFormatted());
}
}
You can see that I'm returning the observable from the Retrofit call. Then in my presenter I just subscribe to this observable and act accordingly:
#Override
public void getBanners() {
dataHandler
.getBanners()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Response<GetBannersResponse>>() {
#Override
public void onCompleted() {
//act on complete
}
#Override
public void onError(Throwable e) {
//act on error
}
#Override
public void onNext(Response<GetBannersResponse> getBannersResponseResponse) {
//act on result received
}
});
}
My question is related to ViewModel second time returns null wherein I am not getting callback inobserve function if I make a repeated call to server. Following is the code I am using -
#Singleton
public class NetworkInformationViewModel extends AndroidViewModel {
private LiveData<Resource<NetworkInformation>> networkInfoObservable;
private final APIClient apiClient;
#Inject
NetworkInformationViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
getNetworkInformation();
}
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInfoObservable;
}
// making API calls and adding it to Observable
public void getNetworkInformation() {
networkInfoObservable = apiClient.getNetworkInformation();
}
}
In Activity, the ViewModel is defined as followed -
final NetworkInformationViewModel networkInformationViewModel =
ViewModelProviders.of(this, viewModelFactory).get(NetworkInformationViewModel.class);
observeViewModel(networkInformationViewModel);
The observeViewModel function is used to add observable on ViewModel.
public void observeViewModel(final NetworkInformationViewModel networkInformationViewModel) {
networkInformationViewModel.getNetworkInfoObservable()
.observe(this, networkInformationResource -> {
if (networkInformationResource != null) {
if (networkInformationResource.status == APIClientStatus.Status.SUCCESS) {
Timber.d("Got network information data");
} else {
final Throwable throwable = networkInformationResource.throwable;
if (throwable instanceof SocketTimeoutException) {
final NetworkInformation networkInformation = networkInformationResource.data;
String error = null;
if (networkInformation != null) {
error = TextUtils.isEmpty(networkInformation.error) ? networkInformation.reply : networkInformation.error;
}
Timber.e("Timeout error occurred %s %s", networkInformationResource.message, error);
} else {
Timber.e("Error occurred %s", networkInformationResource.message);
}
if (count != 4) {
networkInformationViewModel.getNetworkInformation();
count++;
// Uncommenting following line enables callback to be received every time
//observeViewModel(networkInformationViewModel);
}
}
}
});
}
Uncommenting the following line in above function allows the callback to come everytime, but there has to be a proper way of doing this.
//observeViewModel(networkInformationViewModel);
Please note:-
I don't need RxJava implementation for implementing this.
Right now in getNetworkInformation() you are:
Creating a new LiveData
Updating the the LiveData using setValue
Instead, you should have a single LiveData for APIClient created as a member variable, then in getNetworkInformation() just update that member LiveData.
More generally, your APIClient is a data source. For data sources, you can have them contain member LiveData objects that update when the data changes. You can provide getters to those LiveData objects to make them accessible in ViewModels, and ultimately listen to them in your Activities/Fragments. This is similar how you might take another data source, such as Room, and listen to a LiveData returned by Room.
So the code in this case would look like:
#Singleton
public class APIClient {
private final MutableLiveData<Resource<NetworkInformation>> mNetworkData = new MutableLiveData<>(); // Note this needs to be MutableLiveData so that you can call setValue
// This is basically the same code as the original getNetworkInformation, instead this returns nothing and just updates the LiveData
public void fetchNetworkInformation() {
apiInterface.getNetworkInformation().enqueue(new Callback<NetworkInformation>() {
#Override
public void onResponse(
#NonNull Call<NetworkInformation> call, #NonNull Response<NetworkInformation> response
) {
if (response.body() != null && response.isSuccessful()) {
mNetworkData.setValue(new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null));
} else {
mNetworkData.setValue(new Resource<>(APIClientStatus.Status.ERROR, null, response.message()));
}
}
#Override
public void onFailure(#NonNull Call<NetworkInformation> call, #NonNull Throwable throwable) {
mNetworkData.setValue(
new Resource<>(APIClientStatus.Status.ERROR, null, throwable.getMessage(), throwable));
}
});
}
// Use a getter method so that you can return immutable LiveData since nothing outside of this class will change the value in mNetworkData
public LiveData<Resource<NetworkInformation>> getNetworkData(){
return mNetworkData;
}
}
Then in your ViewModel...
// I don't think this should be a Singleton; ViewModelProviders will keep more than one from being instantiate for the same Activity/Fragment lifecycle
public class SplashScreenViewModel extends AndroidViewModel {
private LiveData<Resource<NetworkInformation>> networkInformationLiveData;
#Inject
SplashScreenViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
// Initializing the observable with empty data
networkInfoObservable = apiClient.getNetworkData()
}
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInformationLiveData;
}
}
Your activity can be the same as you originally coded it; it will just get and observe the LiveData from the ViewModel.
So what is Transformations.switchMap for?
switchMap isn't necessary here because you don't need to change the underlying LiveData instance in APIClient. This is because there's really only one piece of changing data. Let's say instead your APIClient needed 4 different LiveData for some reason, and you wanted to change which LiveData you observed:
public class APIClient {
private MutableLiveData<Resource<NetworkInformation>> mNetData1, mNetData2, mNetData3, mNetData4;
...
}
Then let's say that your fetchNetworkInformation would refer to different LiveData to observe depending on the situation. It might look like this:
public LiveData<Resource<NetworkInformation>> getNetworkInformation(int keyRepresentingWhichLiveDataToObserve) {
LiveData<Resource<NetworkInformation>> currentLiveData = null;
switch (keyRepresentingWhichLiveDataToObserve) {
case 1:
currentLiveData = mNetData1;
break;
case 2:
currentLiveData = mNetData2;
break;
//.. so on
}
// Code that actually changes the LiveData value if needed here
return currentLiveData;
}
In this case the actual LiveData coming from getNetworkInformation is changes, and you're also using some sort of parameter to determine which LiveData you want. In this case, you'd use a switchMap, because you want to make sure that the observe statement you called in your Activity/Fragment observes the LiveData returned from your APIClient, even if you change the underlying LiveData instance. And you don't want to call observe again.
Now this is a bit of an abstract example, but it's basically what your calls to a Room Dao do -- if you have a Dao method that queries your RoomDatabase based on an id and returns a LiveData, it will return a different LiveData instance based on the id.
I didn't met the same issue, but i came across a similar thing where the number of observers were increasing each time i was saving the data in db. The way i debugged was how many instances or different instances of observers were getting invoked and i came to know that when you are fetching the live data from view model it needs to be checked for non null or you can say only 1 instance is being returned -
private LiveData<T> data;
public LiveData<T> getLiveData(){
if(data ==null){
data = //api call or fetch from db
}
return data;
}
Before i was simply returning the data object and then after checking the source i came to the conclusion that livedata automatically updates your object and everytime without the null check new instance was getting created and new observers were getting attached. Someone can correct me if my understanding regarding livedata is wrong.
I have already updated the linked question's answer. Re-posting here since I have placed a bounty on the question and hopefully someone will verify that this is the proper way to handle the issue.
Following is the updated working solution -
#Singleton
public class SplashScreenViewModel extends AndroidViewModel {
private final APIClient apiClient;
// This is the observable which listens for the changes
// Using 'Void' since the get method doesn't need any parameters. If you need to pass any String, or class
// you can add that here
private MutableLiveData<Void> networkInfoObservable;
// This LiveData contains the information required to populate the UI
private LiveData<Resource<NetworkInformation>> networkInformationLiveData;
#Inject
SplashScreenViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
// Initializing the observable with empty data
networkInfoObservable = new MutableLiveData<Void>();
// Using the Transformation switchMap to listen when the data changes happen, whenever data
// changes happen, we update the LiveData object which we are observing in the MainActivity.
networkInformationLiveData = Transformations.switchMap(networkInfoObservable, input -> apiClient.getNetworkInformation());
}
/**
* Function to get LiveData Observable for NetworkInformation class
* #return LiveData<Resource<NetworkInformation>>
*/
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInformationLiveData;
}
/**
* Whenever we want to reload the networkInformationLiveData, we update the mutable LiveData's value
* which in turn calls the `Transformations.switchMap()` function and updates the data and we get
* call back
*/
public void setNetworkInformation() {
networkInfoObservable.setValue(null);
}
}
The Activity's code will be updated as -
final SplashScreenViewModel splashScreenViewModel =
ViewModelProviders.of(this, viewModelFactory).get(SplashScreenViewModel.class);
observeViewModel(splashScreenViewModel);
// This function will ensure that Transformation.switchMap() function is called
splashScreenViewModel.setNetworkInformation();
Watch her droidCon NYC video for more information on LiveData. The official Google repository for LiveData is https://github.com/googlesamples/android-architecture-components/ look for GithubBrowserSample project.
The apiClient.getNetworkInformation() call doesn't need it any parameters to get additional information. Hence, the 'Void' added in MutableLiveData.
public LiveData<Resource<NetworkInformation>> getNetworkInformation() {
final MutableLiveData<Resource<NetworkInformation>> data = new MutableLiveData<>();
apiInterface.getNetworkInformation().enqueue(new Callback<NetworkInformation>() {
#Override
public void onResponse(
#NonNull Call<NetworkInformation> call, #NonNull Response<NetworkInformation> response
) {
if (response.body() != null && response.isSuccessful()) {
data.setValue(new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null));
} else {
data.setValue(new Resource<>(APIClientStatus.Status.ERROR, null, response.message()));
}
}
#Override
public void onFailure(#NonNull Call<NetworkInformation> call, #NonNull Throwable throwable) {
data.setValue(
new Resource<>(APIClientStatus.Status.ERROR, null, throwable.getMessage(), throwable));
}
});
return data;
}
Is there any way to enforce non-nullability of LiveData values? Default Observer implementation seems to have #Nullable annotation which forces an IDE to suggest that the value might be null and should be checked manually:
public interface Observer<T> {
/**
* Called when the data is changed.
* #param t The new data
*/
void onChanged(#Nullable T t);
}
A new option is available if you use Kotlin. You can replace LiveData with StateFlow. It is more suitable for Kotlin code and provides built-in null safety.
Instead of using:
class MyViewModel {
val data: LiveData<String> = MutableLiveData(null) // the compiler will allow null here!
}
class MyFragment: Fragment() {
model.data.observe(viewLifecycleOwner) {
// ...
}
}
You can use:
class MyViewModel {
val data: StateFlow<String> = MutableStateFlow(null) // compilation error!
}
class MyFragment: Fragment() {
lifecycleScope.launch {
model.data.collect {
// ...
}
}
}
StateFlow is part of coroutines and to use the lifecycleScope you need to add the lifecycle-extensions dependency:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
Note that this API has been experimental before coroutines 1.4.0.
Here's some additional reading about replacing LiveData with StateFlow.
As Igor Bubelov pointed out, another advantage of this approach is that it's not Android specific so it can be used in shared code in multiplatform projects.
If you use Kotlin, you can create much nicer non-null observe function with extension. There is an article about it. https://medium.com/#henrytao/nonnull-livedata-with-kotlin-extension-26963ffd0333
It's possible to do it safely only if you are in control of the code which sets the data because you'll also have to wrap the LiveData class. This way the data setting methods will be protected with #NonNull and you can be sure that the data has already been checked before reaching the Observer.
Wrap the LiveData class:
public class NonNullMutableLiveData<T> extends MutableLiveData<T> implements NonNullLiveData<T> {
private final #NonNull T initialValue;
public NonNullMutableLiveData(#NonNull T initialValue) {
this.initialValue = initialValue;
}
#Override
public void postValue(#NonNull T value) {
super.postValue(value);
}
#Override
public void setValue(#NonNull T value) {
super.setValue(value);
}
#NonNull
#Override
public T getValue() {
//the only way value can be null is if the value hasn't been set yet.
//for the other cases the set and post methods perform nullability checks.
T value = super.getValue();
return value != null ? value : initialValue;
}
//convenience method
//call this method if T is a collection and you modify it's content
public void notifyContentChanged() {
postValue(getValue());
}
public void observe(#NonNull LifecycleOwner owner, #NonNull NonNullObserver<T> observer) {
super.observe(owner, observer.getObserver());
}
}
Create an interface for exposing as immutable:
public interface NonNullLiveData<T> {
#NonNull T getValue();
void observe(#NonNull LifecycleOwner owner, #NonNull NonNullObserver<T> observer);
}
Finally, wrap the Observer:
//not implementing Observer<T> to make sure this class isn't passed to
//any class other than NonNullMutableLiveData.
public abstract class NonNullObserver<T> {
public Observer<T> getObserver() {
return new ActualObserver();
}
public abstract void onValueChanged(#NonNull T t);
private class ActualObserver implements Observer<T> {
#Override
public void onChanged(#Nullable T t) {
//only called through NonNullMutableLiveData so nullability check has already been performed.
//noinspection ConstantConditions
onValueChanged(t);
}
}
}
Now you can create your data like this:
class DataSource {
private NonNullMutableLiveData<Integer> data = new NonNullMutableLiveData<>(0);
public NonNullLiveData<Integer> getData() {
return data;
}
}
And use it like this:
dataSource.getData().observe(this, new NonNullObserver<Integer>() {
#Override
public void onValueChanged(#NonNull Integer integer) {
}
});
Completely null safe.
While there a few things you can do, it is your responsibility to make sure you don't pass null to the LiveData. In addition to that, every 'solution' is more a suppression of the warning, which can be dangerous (if you do get a null value, you might not handle it and Android Studio will not warn you).
Assert
You can add assert t != null;. The assert will not be executed on Android, but Android Studio understands it.
class PrintObserver implements Observer<Integer> {
#Override
public void onChanged(#Nullable Integer integer) {
assert integer != null;
Log.d("Example", integer.toString());
}
}
Suppress the warning
Add an annotation to suppress the warning.
class PrintObserver implements Observer<Integer> {
#Override
#SuppressWarnings("ConstantConditions")
public void onChanged(#Nullable Integer integer) {
Log.d("Example", integer.toString());
}
}
Remove the annotation
This also works in my installation of Android Studio, but it might not work for you, but you could try to just remove the #Nullable annotation from the implementation:
class PrintObserver implements Observer<Integer> {
#Override
public void onChanged(Integer integer) {
Log.d("Example", integer.toString());
}
}
Default methods
It's unlikely you can use this on Android, but purely from a Java perspective, you could define a new interface and add a null check in a default method:
interface NonNullObserver<V> extends Observer<V> {
#Override
default void onChanged(#Nullable V v) {
Objects.requireNonNull(v);
onNonNullChanged(v);
// Alternatively, you could add an if check here.
}
void onNonNullChanged(#NonNull V value);
}
fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, observer: (t: T) -> Unit) {
this.observe(owner, Observer {
it?.let(observer)
})
}
You would have to do some additional work to handle null values that come from the library itself.
For example, when you return a LiveData from a #Dao in Room, like:
#Dao interface UserDao {
#get:Query("SELECT * FROM users LIMIT 1")
val user: LiveData<User>
}
And observe the user live data, it will call the onChanged callback with a null value if there is no user.
So, I have just started experimenting with LiveData - I am busy with a new project, where I am using ViewModel as well as LiveData - with some of the RESTFul services I use to fetch data, they take no parameters and return some data.
A typical setup of the MVVM paradigm with LiveData looks much like this:
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
Now when we leave this activity, and go to a new activity, by using an Intent or some other means, and not pressing the back button (So, finalize is not called) - and then come back to MyActivity - we of course don't fetch the users again, as we should still have that data.
However, what if we did want to fetch them again?
The only way to do this properly, from what I have looked at, seems to call "setValue" on the getUsers() LiveData object
Something like this:
public class MyActivity extends AppCompatActivity {
public void onResume() {
viewModel.setActive(true);
}
}
And the ViewModel would look like this:
private final MutableLiveData<Boolean> activeLiveData = new MutableLiveData<>();
ViewModel(ViewModelRepo repo){
this.repo = repo;
results = Transformations.switchMap(activeLiveData, active ->{
if(active){
return repo.getUsers();
}else {
return AbsentLiveData.create(); //"Null live data"
}
});
}
LiveData<Users>> getUsers() {
return results;
}
//This could be called "update" with no params
void setActive(boolean active) {
activeLiveData.setValue(active);
}
The one reason I have decided to do it like this is because Google does not want us doing this:
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}
private LiveData<String> getPostalCode(String address) {
// DON'T DO THIS
return repository.getPostCode(address);
}
}
For this reason:
If this is the implementation, the UI would need to unregister from
the previous LiveData and re-register to the new instance each time
they call getPostalCode(). Moreover, if the UI is re-created, it
triggers another call to repository.getPostCode() instead of using the
previous call’s result.
Is there a better way to get the ViewModel to "redo" its repo.getUsers() call? Perhaps I could just make a method that says "Update()" instead of "active" but still - its doing the same thing differently.
Well here you're doing the fetching in the creator of the ViewModel, which locks things in place. Usually they'd advise to fetch the data in the getter, if the data is not there already.
So a good option would be to use the regular pattern first :
private MutableLiveData<Users> users = null;
ViewModel(ViewModelRepo repo){
this.repo = repo;
}
LiveData<Users> getUsers() {
if (users = null) {
fetchUsers();
}
return users;
}
public void fetchUsers() {
users.postValue(repo.getUsers());
}
And then from your Activity/Fragment, whenever you feel necessary to "refresh the users", you'd simply call viewModel.fetchUsers();