Looking at this example: https://github.com/googlesamples/android-architecture-components/tree/master/GithubBrowserSample
I've implemented the same pattern in one of my side projects however, I'm facing difficulties getting tests to work as expected.
I'm trying to test my one of my repository classes. The test checks if the repository fetches data from the api and if the value of the observer changes.
Here is the test class
#RunWith(JUnit4.class)
public class TimelineRepositoryTest {
private SharedPreferences sharedPreferences;
private DatabaseDao databaseDao;
private ApiService apiService;
private TimelineRepository timelineRepository;
#Rule
public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
#Before
public void setup() {
sharedPreferences = mock(SharedPreferences.class);
databaseDao = mock(DatabaseDao.class);
apiService = mock(ApiService.class);
timelineRepository = new TimelineRepository(apiService, sharedPreferences, databaseDao);
timelineRepository.appExecutors = new InstantAppExecutors();
}
#Test
public void fetchTimelineWithForceFetch() {
TimelineResponse timelineResponse = new TimelineResponse();
when(sharedPreferences.getLong(PreferenceUtils.PREFERENCE_LAST_TIMELINE_REFRESH, 0)).thenReturn(0L);
when(apiService.retrieveTimeline()).thenReturn(ApiUtil.successCall(timelineResponse));
MutableLiveData<List<Event>> dbData = new MutableLiveData<>();
when(databaseDao.loadEvents()).thenReturn(dbData);
Observer observer = mock(Observer.class);
timelineRepository.getTimelineEvents().observeForever(observer);
verify(observer).onChanged(Resource.loading(null));
verify(observer).onChanged(Resource.success(new ArrayList<Event>());
}
}
Also, here is the actual repository class:
public class TimelineRepository {
#Inject AppExecutors appExecutors;
#Inject #Named("timelineRefreshDurationInMillis") long timelineRefreshDurationInMillis;
private final DatabaseDao databaseDao;
private final SharedPreferences sharedPreferences;
private final ApiService apiService;
public TimelineRepository(ApiService apiService, SharedPreferences sharedPreferences, DatabaseDao databaseDao) {
this.apiService = apiService;
this.sharedPreferences = sharedPreferences;
this.databaseDao = databaseDao;
}
public LiveData<Resource<List<Event>>> getTimelineEvents() {
return new NetworkBoundResource<List<Event>, TimelineResponse>(appExecutors) {
#Override
protected void saveCallResult(#NonNull TimelineResponse timelineResponse) {
if (timelineResponse.events != null) {
databaseDao.saveEvents(timelineResponse.events);
}
PreferenceUtils.storeLastTimelineRefreshTimeInMillis(sharedPreferences, System.currentTimeMillis());
}
#Override
protected boolean shouldFetch(#Nullable List<Event> data) {
return System.currentTimeMillis() - PreferenceUtils.getLastTimelineRefreshTimeInMillis(sharedPreferences) > timelineRefreshDurationInMillis;
}
#NonNull
#Override
protected LiveData<List<Event>> loadFromDb() {
return databaseDao.loadEvents();
}
#NonNull
#Override
protected LiveData<ApiResponse<TimelineResponse>> createCall() {
return apiService.retrieveTimeline();
}
}.getAsLiveData();
}
}
I want to use the test to check if the mocked observer is called multiple times with different values. However, the test says that it is only called one with the loading argument.
After some debugging it seems like the NetworkBoundResource's https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/repository/NetworkBoundResource.java#L48 observer registered in the constructor is not called.
Has anyone faced this issue?
Related
In my app, I have a ViewModel looks like that:
public class MyExampleViewModel {
private LiveData<MyEntity> myLiveData;
#Inject
MyRepository myRepository;
#Inject
public MyExampleViewModel() {
}
public void init(final Long id) {
if (this.myLiveData == null) {
this.myLiveData = myRepository.getById(id);
}
}
public void toggleStar() NullPointerException {
final MyEntity myValue = this.myLiveData.getValue();
myValue.setStar(!myValue.getStar());
myRepository.save(myValue);
}
}
Also the code of MyRepository#getById (myDao is a room DAO and it is injected):
public LiveData<MyEntity> getById(final Long id) {
return myDao.getById(id);
}
The code of MyDao#getById:
#Query(
"SELECT * FROM myTable WHERE id=:id"
)
LiveData<MyEntity> getById(final Long id);
I also try to test this ViewModel using
myExampleViewModel.init(myId);
myExampleViewModel.toggleStar();
but after the init call my LiveData value is always null.
My first question is: is it a best practice to use getValue() on my LiveData or should I use Transformation.map?
My second question is: in my test, how can I have a LiveData populated? I tried to use CountingTaskExecutorRule and InstantTaskExecutorRule but without any success.
Thank you for your help!
I understood why myLiveData is not populated in my test. According to the documentation "LiveData objects that are lazily calculated on demand." and LiveData#getValue only get the value if the LiveData is already populated but doesn't calculate the value.
So I fixed my test adding a getter on my LiveData and an observer on my LiveData to force the calculation like that LiveDataUtil.getValue(myExampleViewModel.getMyLiveData()); with LiveDataUtil#getValue:
public class LiveDataUtil {
public static <T> T getValue(final LiveData<T> liveData) throws InterruptedException {
final Object[] data = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
#Override
public void onChanged(#Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
new Handler(Looper.getMainLooper()).post(() -> liveData.observeForever(observer));
latch.await(2, TimeUnit.SECONDS);
//noinspection unchecked
return (T) data[0];
}
}
After this fix, MyExampleViewModel class looks like:
public class MyExampleViewModel {
private LiveData<MyEntity> myLiveData;
#Inject
MyRepository myRepository;
#Inject
public MyExampleViewModel() {
}
public void init(final Long id) {
if (this.myLiveData == null) {
this.myLiveData = myRepository.getById(id);
}
}
public void toggleStar() NullPointerException {
final MyEntity myValue = this.myLiveData.getValue();
myValue.setStar(!myValue.getStar());
myRepository.save(myValue);
}
public LiveData<MyEntity> getMyLiveData() {
return myLiveData;
}
}
And my test method:
myExampleViewModel.init(myId);
LiveDataUtil.getValue(myExampleViewModel.getMyLiveData());
myExampleViewModel.toggleStar();
I fixed my test but I still don't know if using LiveData.getValue is a best practice and I found few documentation on this topic. So, I'm interested in this topic if you have more information.
I am trying to implement clean architecture in Android (Java, not Kotlin). Specifically I want to implement the MVVMi pattern, explained in this post.
Other tools I am using:
Hilt, for dependency injection.
Retrofit / RXJava
Room Database
I have the code put together like this:
Interactor
public class BreviarioInteractor {
#Inject
public BreviarioRepository mRepository;
public Observable<JsonElement> searchBreviario(String query, HashMap<String,String> map) {
Log.d("BreviarioInteractor","a");
return new GetBreviarioUseCaseImpl(mRepository).getBreviario(query,map);
}
}
UseCase
public class GetBreviarioUseCaseImpl {
#Inject
public BreviarioRepository mRepository;
public GetBreviarioUseCaseImpl(BreviarioRepository mRepository) {
this.mRepository = mRepository;
//mRepository is NULL here
Log.d("aaa",this.mRepository.toString());
}
public Observable<JsonElement> getBreviario(String query, HashMap<String,String> map) {
return mRepository.getBreviario(query,map);
}
}
Repository
public class BreviarioRepository {
private static final String TAG = "BreviarioRepository";
ApiService apiService;
#Inject
public BreviarioRepository(ApiService apiService) {
this.apiService = apiService;
}
public Observable<JsonElement> getBreviario(String query, HashMap<String,String> map){
Log.d("abcd-2","b");
return apiService.getBreviario(query,map);
}
}
ViewModel
public class BreviarioViewModel extends ViewModel {
private static final String TAG = "BreviarioViewModel";
private BreviarioRepository breviarioRepository;
private BreviarioInteractor breviarioInteractor;
private final CompositeDisposable disposables = new CompositeDisposable();
private final MutableLiveData<ApiResponse> mLiveData = new MutableLiveData<>();
#ViewModelInject
public BreviarioViewModel(BreviarioRepository breviarioRepository) {
this.breviarioRepository = breviarioRepository;
breviarioInteractor = new BreviarioInteractor();
}
// ...
public void loadBreviario(String theDate, HashMap<String, String> map) {
Log.d("abcd1","a");
disposables.add(breviarioInteractor.searchBreviario(theDate, map)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe((d) -> mLiveData.setValue(ApiResponse.loading()))
.subscribe(
result -> mLiveData.setValue(ApiResponse.success(result)),
throwable -> mLiveData.setValue(ApiResponse.error(throwable))
));
}
//...
#Override
protected void onCleared() {
super.onCleared();
disposables.clear();
}
}
The problem
My problem here is that the repository is null in the UseCase.
I appreciate any help solving this problem as well as any suggestions to improve my code.
I've been looking for examples that implement MVVMi with no success. Most of the examples are based on Kotlin. I found this repository in Java code, but it uses Dagger for dependency injection and the repository is implemented differently, it uses Converters, etc.
I'm just getting started with clean architecture as well as RXJava so I'm a bit lost on all of this.
I have found the problem: I was not injecting the repository correctly into the interactor.
As the documentation explains, the object should be passed to the constructor and annotated with #Inject
Interactor
public class BreviarioInteractor {
private final BreviarioRepository mRepository;
#Inject
public BreviarioInteractor(BreviarioRepository mRepository) {
this.mRepository = mRepository;
}
Then in the ViewModel I have to pass the repository in parameter when I create the Interactor:
ViewModel
#ViewModelInject
public BreviarioViewModel(BreviarioRepository breviarioRepository) {
this.breviarioRepository = breviarioRepository;
breviarioInteractor = new BreviarioInteractor(breviarioRepository);
}
I am new to MVVM and trying to clear my rxJava disposables, i have seen some answers saying to clear it in ViewModel in onClear method but how do i get to add the disposable in the first place ?
//Repository Code
public class MyRepository {
public MutableLiveData<String> deleteDraftById(int recordId {
final MutableLiveData<String> result = new MutableLiveData<>();
Completable deleteDraftById = completedDao.deleteDraftById(recordId);
deleteDraftById.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
result.setValue("1");
}
#Override
public void onError(Throwable e) {
result.setValue(e.getMessage());
}
});
return result;
}
}
//ViewModel
public class MyViewModel extends AndroidViewModel {
public MutableLiveData<String> deleteDraftById(int recordId){
return myRepository.deleteDraftById(recordId);
}
}
In my opinion nothing wrong with using live data in repos, for example if single source of truth is needed. Here is what I'd suggested (rxjava 1.x assumed, pseudocode a-la java) :
public class MyRepository {
public final MutableLiveData<String> result = new MutableLiveData<>();
public Completable deleteDraftById(int recordId) {
return completedDao.deleteDraftById(recordId)
.doOnSubscribe(...) //potentially report progress start, if needed
.doOnSuccess(...) //report success to your live data aka result.value = ...
.onErrorComplete(...) //report error to your live data and complete
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
}
public class MyViewModel(....pass MyRepository) extends AndroidViewModel {
//expose live data from repo somehow, may be like this:
public final LiveData<String> abc = myRepository.result;
private final CompositeSubscription compositeSubscription = new CompositeSubscription();
//call this from ui
public void delete(int recordId) {
compositeSubscription.add(
myRepository
.deleteDraftById(recordId)
.subscribe()
)
}
#Override
protected void onCleared() {
super.onCleared();
compositeSubscription.clear();
}
}
I am trying to follow Android architecture guidelines to make this app. I have a MovieRepository which is responsible for fetching JSON (data layer), and I have a ViewModel that supplies data to the UI in my MainActivity. I am using retrofit 2 for my networking task.
MovieRespository code:
public class MovieRepository {
private static final String TAG = MovieRepository.class.getSimpleName();
public LiveData<ReturnMovie> search(String term) {
final MutableLiveData<ReturnMovie> data = new MutableLiveData<>();
MovieService service = ServiceGenerator.createService(MovieService.class);
Call<ReturnMovie> call = service.requestMovie(term, MovieAPIUtils.KEY);
call.enqueue(new Callback<ReturnMovie>() {
#Override
public void onResponse(Call<ReturnMovie> call, Response<ReturnMovie> response) {
ReturnMovie movie = response.body();
data.setValue(movie);
Log.d(TAG, data.getValue().getPage().toString());
}
#Override
public void onFailure(Call<ReturnMovie> call, Throwable t) {
Log.d(TAG, t.toString());
}
});
return data;
}
}
MovieService:
public interface MovieService {
#GET(MovieAPIUtils.Path.MOVIE_PATH + "/{param}")
Call<ReturnMovie> requestMovie (#Path("param") String endpoints,
#Query(MovieAPIUtils.Query.API_QUERY) String key);
}
ViewModel:
public class MainActivityMovieViewModel extends ViewModel {
private static final String TAG = MainActivityMovieViewModel.class.getSimpleName();
private LiveData<ReturnMovie> movie;
private MovieRepository repo = new MovieRepository();
public LiveData<ReturnMovie> getMovie(String searchTerm) {
if (movie == null) {
movie = repo.search(searchTerm);
}
return movie;
}
}
MainActivity:
public class MainActivity extends AppCompatActivity implements MovieAdapter.MovieOnClickListener {
private RecyclerView mRecyclerView;
private MovieAdapter mMovieAdapter;
private MainActivityMovieViewModel viewModel;
private static final String TAG = MainActivity.class.getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewModel = ViewModelProviders.of(this).get(MainActivityMovieViewModel.class);
initRecyclerViewWithMovies();
ReturnMovie movie = viewModel.getMovie(MovieAPIUtils.Endpoints.POPULAR_ENDPOINT).getValue();
// other code....
}
In the onResponse(new Callback<ReturnMovie>) I was able to retrieve the movie object and I proved it by logging one of its property values, so there IS a valid ReturnMovie object. However, in my MainActivity, the method ReturnMovie movie = viewModel.getMovie(MovieAPIUtils.Endpoints.POPULAR_ENDPOINT).getValue(); gives me a null. I checked everywhere is just cannot see where the problem is.
The rest of the code is on my Github:
https://github.com/brendoncheung/PopularMovie/tree/mvvm_approach
You use android architecture components, which uses observable pattern in live data. If you are just getting the current value of livedata, you are not sure it has been processed yet.
Instead of
ReturnMovie movie = viewModel.getMovie(MovieAPIUtils.Endpoints.POPULAR_ENDPOINT).getValue(); i think you should use
viewModel.getMovie(MovieAPIUtils.Endpoints.POPULAR_ENDPOINT).observe(this, new Observer<ReturnMovie>() {
#Override
public void onChanged(#Nullable ReturnMovie movie) {
//do stuff with the movie
doSomething(movie);
}
});
You don't have to manage unsubscription since architecture component manages unsubscription for activities itself
Take a look on : https://developer.android.com/topic/libraries/architecture/livedata
So according to android developers: "Architecture Components provides ViewModel helper class for the UI controller that is responsible for preparing data for the UI. ViewModel objects are automatically retained during configuration changes so that data they hold is immediately available to the next activity or fragment instance."
In the code below there is an asynchronous class that gets called in deleteItem function. My question is this: Does ViewModel also handles the asynchronous calls made inside it or will cause memory leaks?
Thank you
public class BorrowedListViewModel extends AndroidViewModel {
private final LiveData<List<BorrowModel>> itemAndPersonList;
private AppDatabase appDatabase;
public BorrowedListViewModel(Application application) {
super(application);
appDatabase = AppDatabase.getDatabase(this.getApplication());
itemAndPersonList = appDatabase.itemAndPersonModel().getAllBorrowedItems();
}
public LiveData<List<BorrowModel>> getItemAndPersonList() {
return itemAndPersonList;
}
public void deleteItem(BorrowModel borrowModel) {
new deleteAsyncTask(appDatabase).execute(borrowModel);
}
private static class deleteAsyncTask extends AsyncTask<BorrowModel, Void, Void> {
private AppDatabase db;
deleteAsyncTask(AppDatabase appDatabase) {
db = appDatabase;
}
#Override
protected Void doInBackground(final BorrowModel... params) {
db.itemAndPersonModel().deleteBorrow(params[0]);
return null;
}
}
}
I would provide an example, probably you need to modify the code.
First you need a live data change and subscribe to that in your view. Then in the controller you post the value telling the subscriber that something appends. This way asynchronously the view would get alerted.
private MutableLiveData<String> databaseLiveData = new MutableLiveData<>();
...
And in the deleteAsyncTask class you can add:
protected void onPostExecute(Void result) {
databaseLiveData.postValue("some data deleted");
}
And in the BorrowedListViewModel class this method to access from the view add this method:
public LiveData<String> getChanger() {
return databaseLiveData;
}
In the view e.g.Activity add this:
private BorrowedListViewModel mBorrowedListViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
BorrowedListViewModel = ViewModelProviders.of(this).get(BorrowedListViewModel.class);
subscribe();
}
private void subscribe() {
final Observer<String> liveDataChange = new Observer<String>() {
#Override
public void onChanged(#Nullable final String message) {
Log.d("Activity", message);
}
};
liveDataChange.getChanger().observe(this, liveDataChange);
}
Hope this help.