My ViewModel class looks like this:
public class ViewModelMainActivity extends AndroidViewModel {
private LocalRepository localRepository;
private LiveData<List<Task>> allJob;
private LiveData<List<Task>> allShopping;
private LiveData<List<Task>> allOther;
public ViewModelMainActivity(#NonNull Application application) {
super(application);
localRepository = new LocalRepository(application);
allJob = localRepository.getAllJob();
allShopping = localRepository.getAllShopping();
allOther = localRepository.getAllOther();
}
public void insert(Task task) {
localRepository.insert(task);
}
public void delete(Task task) {
localRepository.delete(task);
}
public LiveData<List<Task>> getAllJob() {
return allJob;
}
public LiveData<List<Task>> getAllShopping() {
return allShopping;
}
public LiveData<List<Task>> getAllOther() {
return allOther;
}
}
Then in MainActivity calls two methods:
private void getAllJob() {
viewModelMainActivity.getAllJob().observe(this, new Observer<List<Task>>() {
#Override
public void onChanged(List<Task> tasks) {
if(tasks.size() == 0) {
linearLayoutActivityMain.setVisibility(View.VISIBLE);
} else {
linearLayoutActivityMain.setVisibility(View.INVISIBLE);
}
taskAdapter.setAllJobTasks(tasks);
}
});
}
private void getAllShopping() {
viewModelMainActivity.getAllShopping().observe(this, new Observer<List<Task>>() {
#Override
public void onChanged(List<Task> tasks) {
Log.i("Size", "Shopping: " + String.valueOf(tasks.size()));
if(tasks.size() == 0) {
linearLayoutActivityMain.setVisibility(View.VISIBLE);
} else {
linearLayoutActivityMain.setVisibility(View.INVISIBLE);
}
taskAdapter.setCurrentTasks(tasks);
}
});
}
Why when I save a task:
viewModelMainActivity.insert(task);
e.g. to the job category, both onChanged methods are called, not just the onChanged method in getAllJob.
How could I separate it? That only the onChanged method would be called for values that have changed. Should I create separate ViewModels objects? But what about saving the task then? I would have to call the insert method three times for each object?
Related
I'm trying to implement MVVM pattern usingGoogle's android architecture components while using RX Java in NetworkBoundResource. However I'm having a difficult time finding a way to communicate the error response from network call to activity.
here is a link to the original github project.
I have read this post about "Refactoring google's NetworkBoundResource class to use RxJava instead of LiveData" but still not clear how to actually solve the problem.
would appreciate if you could direct me to a code based solution for this scenario for better understanding.
cheers!
GithubRepository:
#Singleton
public class GithubRepository {
private GithubDao githubDao;
private GithubApiService githubApiService;
public GithubRepository(GithubDao githubDao, GithubApiService githubApiService) {
this.githubDao = githubDao;
this.githubApiService = githubApiService;
}
public Observable<Resource<List<GithubEntity>>> getRepositories(Long page) {
return new NetworkBoundResource<List<GithubEntity>, GithubApiResponse>() {
#Override
protected void saveCallResult(#NonNull GithubApiResponse item) {
List<GithubEntity> repositories = item.getItems();
for (GithubEntity githubEntity : repositories) {
githubEntity.setPage(page);
githubEntity.setTotalPages(item.getTotalCount());
}
githubDao.insertRepositories(repositories);
}
#Override
protected boolean shouldFetch() {
return true;
}
#NonNull
#Override
protected Flowable<List<GithubEntity>> loadFromDb() {
List<GithubEntity> repositories = githubDao.getRepositoriesByPage(page);
return (repositories == null || repositories.isEmpty()) ?
Flowable.empty() : Flowable.just(repositories);
}
#NonNull
#Override
protected Observable<Resource<GithubApiResponse>> createCall() {
return githubApiService.fetchRepositories(QUERY_SORT, QUERY_ORDER, page)
.flatMap(response ->
Observable.just(response.isSuccessful()
? Resource.success(response.body())
: Resource.error("", new GithubApiResponse())));
}
}.getAsObservable();
}
}
.:UPDATE:.
So I can see that the the in NetworkBoundResource the doOnError and onErrorResumeNext are handling the error response. but i don't know how to get this response in the activity. I am having a hard time understanding it.
NetworkBoundResource:
public abstract class NetworkBoundResource<ResultType, RequestType> {
private Observable<Resource<ResultType>> result;
#MainThread
protected NetworkBoundResource() {
Observable<Resource<ResultType>> source;
if (shouldFetch()) {
source = createCall()
.subscribeOn(Schedulers.io())
.doOnNext(apiResponse -> saveCallResult(processResponse(apiResponse)))
.flatMap(apiResponse -> loadFromDb().toObservable().map(Resource::success))
.doOnError(t -> onFetchFailed())
.onErrorResumeNext(t -> {
return loadFromDb()
.toObservable()
.map(data -> Resource.error(t.getMessage(), data));
})
.observeOn(AndroidSchedulers.mainThread());
} else {
source = loadFromDb()
.toObservable()
.map(Resource::success);
}
result = Observable.concat(
loadFromDb()
.toObservable()
.map(Resource::loading)
.take(1),
source
);
}
public Observable<Resource<ResultType>> getAsObservable() {return result;}
protected void onFetchFailed() {}
#WorkerThread
protected RequestType processResponse(Resource<RequestType> response) {return response.data;}
#WorkerThread
protected abstract void saveCallResult(#NonNull RequestType item);
#MainThread
protected abstract boolean shouldFetch();
#NonNull
#MainThread
protected abstract Flowable<ResultType> loadFromDb();
#NonNull
#MainThread
protected abstract Observable<Resource<RequestType>> createCall();
}
GithubListViewModel:
public class GithubListViewModel extends ViewModel {
private Long currentPage = 0l;
private GithubRepository repository;
private List<GithubEntity> repositories = new ArrayList<>();
private SingleLiveEvent<List<GithubEntity>> repoListLiveData = new SingleLiveEvent<>();
#Inject
public GithubListViewModel(GithubDao githubDao, GithubApiService githubApiService) {
repository = new GithubRepository(githubDao, githubApiService);
}
public void fetchRepositories() {
repository.getRepositories(++currentPage)
.subscribe(resource -> {
if(resource.isLoaded()) {
repositories.addAll(resource.data);
getRepositoryListLiveData().postValue(resource.data);
}
});
}
public List<GithubEntity> getRepositories() {
return repositories;
}
public SingleLiveEvent<List<GithubEntity>> getRepositoryListLiveData() {
return repoListLiveData;
}
public boolean isLastPage() {
return getRepositoryListLiveData().getValue() != null &&
!getRepositoryListLiveData().getValue().isEmpty() ?
getRepositoryListLiveData().getValue().get(0).isLastPage() :
false;
}
}
GithubActivity:
public class GithubListActivity extends AppCompatActivity implements RecyclerLayoutClickListener {
...
private void initialiseViewModel() {
githubListViewModel = ViewModelProviders.of(this, viewModelFactory).get(GithubListViewModel.class);
githubListViewModel.getRepositoryListLiveData().observe(this, repositories -> {
if(githubListAdapter.getItemCount() == 0) {
if(!repositories.isEmpty()) {
animateView(repositories);
} else displayEmptyView();
} else if(!repositories.isEmpty()) displayDataView(repositories);
});
}
...
githubListViewModel.fetchRepositories();
...
}
Trying to learn Room and RXJAVA.
I have about 80% of this understood but I'm getting stuck on figuring the rest out.
Here is the error I get on the insert data.
java.lang.NullPointerException: Attempt to invoke interface method
'void
com.example.learnroom.EntityDao.insert(com.example.learnroom.Entitys)'
on a null object reference
If I don't run the try catch I get the following error which seems to be related.
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example.learnroom/com.example.learnroom.MainActivity}:
java.lang.NullPointerException: Attempt to invoke interface method
'io.reactivex.Maybe
com.example.learnroom.EntityDao.getEntity(java.lang.String)' on a null
object reference
How do I fix this?
I have tried to simplify from the tutorials all over the web most using recyclerviews to just 2 text fields. They say this is 3 pieces but it doesn't seem like it, as the DB was never set up so I ran it in a method to run the code. Maybe someone can help explain to me how this really works.
my code
Dao
public interface EntityDao {
#Query("SELECT * FROM Entitys WHERE ID = :ID LIMIT 1")
Maybe<List<Entitys>> getEntity(String ID);
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Entitys entitys);
#Query("DELETE FROM Entitys")
void deleteAllEntity();
}
Entity
public class Entitys {
#PrimaryKey
#NonNull
public String ID;
public String ts;
public String tss;
public Entitys(#NonNull String ID, String ts, String tss) {
this.ID = ID;
this.ts = ts;
this.tss = tss;
}
public String getTss() {
return tss;
}
public void setTss(String tss) {
this.tss = tss;
}
public void setID(String ID) {
this.ID = ID;
}
public void setTs(String ts) {
this.ts = ts;
}
public String getID() {
return ID;
}
public String getTs() {
return ts;
}
}
database
#Database(entities = {Entitys.class}, version = 1)
public abstract class PathwaysDB extends RoomDatabase {
private static volatile PathwaysDB INSTANCE;
public static EntityDao entityDao() {
return null;
}
public static PathwaysDB getInstance(Context context) {
if (INSTANCE == null) {
synchronized (PathwaysDB.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
PathwaysDB.class, "Pathwaysdb")
.build();
}
}
}
return INSTANCE;
}
}
MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
Button tb;
EditText te, tes;
String ts, tss, ID;
CompositeDisposable compositeDisposable = new CompositeDisposable();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ID ="test";
te = findViewById(R.id.te);
tb = findViewById(R.id.tb);
tb.setOnClickListener(this);
tes = findViewById(R.id.tes);
Builddb();
try{
getData();}catch (Exception e){}
}
private void Builddb() {
Completable.fromAction(() -> PathwaysDB.getInstance(this))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onComplete() {
// action was completed successfully
}
#Override
public void onError(Throwable e) {
// something went wrong
}
});
}
private void getData() {
Maybe<List<Entitys>> single = entityDao().getEntity(ID);
single.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new MaybeObserver<List<Entitys>>() {
#Override
public void onSubscribe(Disposable d) {
// add it to a CompositeDisposable
}
#Override
public void onSuccess(List<Entitys> entity) {
te.setText(entity.indexOf(ts));
tes.setText(entity.indexOf(tss));
}
#Override
public void onError(Throwable e) {
// show an error message
}
#Override
public void onComplete() {
}
});
compositeDisposable.add((Disposable) single);
}
#Override
protected void onDestroy() {
super.onDestroy();
compositeDisposable.dispose();
}
private void updateUserName() {
ts = te.getText().toString();
tss = tes.getText().toString();
Entitys entitys = new Entitys(ID, ts, tss);
Completable.fromAction(() -> entityDao().insert(entitys))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onComplete() {
// action was completed successfully
}
#Override
public void onError(Throwable e) {
// something went wrong
}
});
}
#Override
public void onClick(View view) {
updateUserName();
Intent forward = new Intent(this, secondpage.class);
startActivity(forward);
}
}
Reason for crash is this line in your PathwaysDB class
public static EntityDao entityDao() {
return null;
}
it is returning null. It should be like
public abstract EntityDao entityDao()
You forget to add #Dao annonation to your EntityDao interface class.
also you need to change below method :
public static EntityDao entityDao() {
return null;
}
To
public abstract EntityDao entityDao();
I'm new in android architecture component. this is my code , i'm at the point that I don't know how to notify my activity and get the results back
these are my codes:
Activity:
private void iniViewModels() {
Observer<List<User>>usersObserver=new Observer<List<User>>() {
#Override
public void onChanged(#Nullable List<User> users) {
Log.v("this","LiveData: ");
for (int i=0;i<users.size();i++){
Log.v("this",users.get(i).getName());
}
}
};
mViewModel = ViewModelProviders.of(this)//of-->name of act or fragment
.get(AcActivityViewModel.class);///get -->the name of viewModelClass
mViewModel.mUsers.observe(this,usersObserver);
}
this is my viewModel Class:
public class IpStaticViewModel extends AndroidViewModel {
public LiveData<List<Ipe>> ips;
private AppRepository repository;
public IpStaticViewModel(#NonNull Application application) {
super(application);
repository=AppRepository.getInstance(application.getApplicationContext());
}
public void getIpStatics() {
repository.getStaticIps();
}
}
this is my repository class:
public class AppRepository {
private static AppRepository ourInstance ;
private Context context;
private IpStaticInterface ipInterface;
public static AppRepository getInstance(Context context) {
if (ourInstance == null) {
ourInstance=new AppRepository(context);
}
return ourInstance;
}
private AppRepository(Context context) {
this.context=context;
}
public void getStaticIps() {
ipInterface= ApiConnection.getClient().create(IpStaticInterface.class);
ipInterface.getIpes()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new SingleObserver<IpStaticList>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(IpStaticList ipStaticList) {
List<Ipe>ips=ipStaticList.getIpes();
}
#Override
public void onError(Throwable e) {
Log.v("this","Eror "+ e.getMessage());
}
});
}
}
I'm using retrofit for fetching the data ,it fetch the data successfully but I don't know how to notify my activity
can you help me?
Have a MutableLiveData
final MutableLiveData<List<Ipe>> data = new MutableLiveData<>();
In onSucess
public MutableLiveData<List<Ipe>> getStaticIps() {
ipInterface= ApiConnection.getClient().create(IpStaticInterface.class);
ipInterface.getIpes()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new SingleObserver<IpStaticList>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(IpStaticList ipStaticList) {
List<Ipe>ips=ipStaticList.getIpes();
data.setValue(ips);
}
#Override
public void onError(Throwable e) {
Log.v("this","Eror "+ e.getMessage());
}
});
return data;
}
In repository expose this to viewmodel
public LiveData<List<Ipe>> getIpStatics() {
return repository.getStaticIps();
}
In Activity you observe the livedata
IpStaticViewModel viewmodel = ViewModelProviders.of(this
.get(IpStaticViewModel.class)
viewModel.getIpStatics().observe(this, new Observer<List<Ipe>>() {
#Override
public void onChanged(#Nullable List<Ipe> ipes) {
if (ipes != null) {
// dosomething
}
}
});
If you want to generalize your response in case you have a error or something have a look at https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.kt
I've never create unit testing before. I'm planning to create UI test & Unit test for my presenter & datasource. I use Retrofit, RxJava, and Dagger in my apps.
Here's what i've tried so far
DataSource (My Datasource is coming from API)
public class DataSource implements DataSourceContract {
private static DataSource dataSource;
#Inject
SharedPreferences sharedPreferences;
#Inject
NewsService newsService;
private DataSource(Context context) {
DaggerAppComponent.builder()
.networkModule(new NetworkModule(API_URL))
.appModule(new AppModule(context.getApplicationContext()))
.preferencesModule(new PreferencesModule())
.build()
.inject(this);
}
public static synchronized DataSource getInstance(Context context) {
if(dataSource == null) {
dataSource = new DataSource(context);
}
return dataSource;
}
public String parseError(Throwable e) {
if(e instanceof SocketTimeoutException) {
return ERROR_TIMEOUT;
}
else if(e instanceof SocketException) {
return ERROR_NO_CONNECTION;
}
else {
return ERROR_SERVER;
}
}
#Override
public DisposableObserver<NewsResponse> getNews(final Callback<NewsResponse> callback) {
return newsService.getNews()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<NewsResponse>() {
#Override
public void onNext(NewsResponse value) {
callback.onSuccess(value);
}
#Override
public void onError(Throwable e) {
callback.onFailure(e);
}
#Override
public void onComplete() {
}
});
}
}
Presenter
public class MainPresenter implements MainContract.Presenter {
private MainContract.View view;
private DataSource dataSource;
private Disposable dispossable;
public MainPresenter(MainContract.View view, DataSource dataSource) {
this.view = view;
this.dataSource = dataSource;
}
#Override
public void onStart() {
getNews();
}
#Override
public void onStop() {
if(dispossable != null && !dispossable.isDisposed()) {
dispossable.dispose();
}
}
#Override
public void getNews() {
view.setLoading(true);
dispossable = dataSource.getNews(new DataSourceContract.Callback<NewsResponse>() {
#Override
public void onSuccess(NewsResponse responseData) {
try {
switch (responseData.getStatus()) {
case API_SUCCESS:
view.setLoading(false);
view.getNewsSuccess(responseData.getArticles());
break;
default:
view.setLoading(false);
view.getNewsFailed(responseData.getStatus());
break;
}
}
catch (Exception e) {
view.setLoading(false);
view.getNewsFailed(ERROR_SERVER);
}
}
#Override
public void onFailure(Throwable e) {
view.setLoading(false);
view.isNetworkFailed(dataSource.parseError(e), false);
}
});
}
}
And this is the test of my presenter
public class MainPresenterTest {
#Mock
DataSource dataSource;
#Mock
MainContract.View view;
MainContract.Presenter presenter;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
presenter = new MainPresenter(view, dataSource);
}
#Test
public void getNews() throws Exception {
List<Article> articleList = new ArrayList<>();
presenter.getNews();
Mockito.verify(view, Mockito.only()).getNewsSuccess(articleList);
}
}
But there is error when I run the test
Wanted but not invoked:
view.getNewsSuccess([]);
-> at com.java.mvp.view.main.MainPresenterTest.getNews(MainPresenterTest.java:37)
I have no problem running this apps on the device, but I can't make it work on testing
Any idea how to fix this presenter test? Am I doing it right?
And how do I test my datasource? I have no idea how to test this one
Thank you
Keep things simple. You are testing your presenter, not the data source. Add new methods to your presenter for the success and error responses. Then add two tests: one for the success and one for the error.
#Override
public void getNews() {
view.setLoading(true);
dispossable = dataSource.getNews(new DataSourceContract.Callback<NewsResponse>() {
#Override
public void onSuccess(NewsResponse responseData) {
onSuccessNewsResponse(responseData);
}
#Override
public void onFailure(Throwable e) {
onErrorNewsResponse(e);
}
});
}
Add #VisibleForTesting annotation to the new methods.
Success test:
#Test
public void getNewsSuccess() {
presenter.onSuccessNewsResponse(your_response);
Mockito.verify(...);
}
Error test:
#Test
public void getNewsError() {
presenter.onErrorNewsResponse(your_error);
Mockito.verify(...);
}
You have to mock also :
dataSource.getNews() using Mockito when :
e.g.
when(dataSource.getNews()).thenReturn(new SuccessCallback());
So you have to lead your test code into the success callback and check there what methods are called.
The same goes with the eroor case.
I have a class ViewModel that exposes a PublishSubject binder.
ViewModel
public class ViewModel {
private PublishSubject<ActionsEvent> binder = PublishSubject.create();
private Service service = createService();
#Override
public Observable<ActionsEvent> getBinder() {
return binder.doOnSubscribe(initialize());
}
private Action0 initialize() {
return new Action0() {
#Override
public void call() {
service.getActions().subscribe(new Action1<Action>() {
#Override
public void call(Action action) {
Log.d(TAG, "So far, so good");
binder.onNext(new ActionFetchedEvent(action));
}
});
}
};
}
}
And in the Activity, it subscribe an action to be executed when each event is fetched.
Activity
public class MyActivity extends Activity {
#Override
public void onCreate(Bundle savedInstance) {
//More code
viewModel.getBinder().subscribe(new Action1<ActionsEvent>() {
#Override
public void call(ActionsEvent event) {
Log.d(TAG, "This is not printed!!");
paintActionInUserInterface(event.getAction());
}
});
}
}
Service
public interface ActionsService {
#GET("/actions")
Observable<Action> getActions(); //Performs an HTTP request with Retrofit
}
ActionFetchedEvent
public class ActionFetchedEvent implements ActionsEvent {
private Action action;
//getters and setters
}
But subscriber doesn't receive the event. Why?
it is because you do not create an Subject with .create() factory-method, and onSubscribe will be called before the callback of your subscription, so you will subscribe too late and miss the element. You could use a BahaviourSubject, which will replay the last element, if you subscribe.
Could you please tell us what you want to achieve, because I think you could compose the observables in a way better way, than subscribing and posting onNext onto the subject.
Please have a look at my example. I use RxJava2 as environment.
public class ViewModelTest {
class ActionsEvent {
}
class ActionFetchedEvent extends ActionsEvent {
public ActionFetchedEvent(ActionsEvent actionEvent) {
}
}
interface Service {
public Observable<ActionsEvent> getActions();
}
class MyViewModel {
private BehaviorSubject<ActionsEvent> binder;
private Service service;
public MyViewModel(Service service) {
this.service = service;
this.binder = BehaviorSubject.create();
}
public Observable<ActionsEvent> getBinder() {
return binder.doOnSubscribe(disposable -> {
service.getActions().subscribe(action -> {
binder.onNext(new ActionFetchedEvent(action));
}
);
});
}
}
#Test
public void name() throws Exception {
Service mock = mock(Service.class);
MyViewModel viewModel = new MyViewModel(mock);
when(mock.getActions()).thenAnswer(invocation -> {
return Observable.just(new ActionsEvent());
});
TestObserver<ActionsEvent> test = viewModel.getBinder().test();
test.assertValueCount(1);
}
}