I'm migrating my app to the rxJava2 and would like to clarify some things.
In my BasePresenter class I do following:
#Override
public void attachView(T mvpView) {
this.mvpView = mvpView;
compositeDisposable = new CompositeDisposable();
}
#Override
public void detachView() {
compositeDisposable.dispose();
mvpView = null;
}
So if I call compositeDisposable.dispose(); when I detach the view then onNext(), onError() or onComplete() are not going to be called and there is no reason to check isViewAttached() in onNext()?
Is this the right way of using CompositeDisposable in the presenter?
Yes, that is correct. CompositeDisposable is essentially what CompositeSubscription was in the previous version of rxJava.
Related
I have an android activity with a fragment.
In the fragment, I fetch data using retrofit and set a static flag, so that , when I again go to this fragment, I restrict fetching data again.
I also store the data in a singleton instance.
But even after I destroyed the activity/closed the application, the static flag and the instance is still available and the list is also present in the instance, which malfunctions my app.
But I want the instance to be created newly and fetch data at each run.
This is my singleton instance.
public class Utilities {
private static Utilities utils = null;
private List<Data> friendsList;
public List<Data> getDataList() {
return dataList;
}
public void setDataList(List<Data> dataList) {
this.dataList = dataList;
}
private List<Data> dataList;
public synchronized static Utilities getInstance(){
if(utils == null){
utils = new Utilities();
}
return utils;
}
}
This is my fragment:
public class DataFragment extends Fragment
{
private static boolean hasObtainedData;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if(!hasObtainedData){
getData(v);
}else{
recyclerView.setAdapter(new Adapter(utils.getDataList()));
}
}
private void getData(View v) {
//get Data using Retrofit:
hasObtainedData = true;
utils.setDataList(dataListObtainedUsingRetrofit)
recyclerView.setAdapter(new Adapter(utils.getDataList()));
}
}
This is how, I call my fragment from MainActivity:
#Override
public void onTabSelected(TabLayout.Tab tab) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentBox, new DataFragment()).commit();
}
I tried giving System.gc() at onDestroy() of MainActivity, but still, the singleton instance is alive.
I search many SOF posts based on this, but wasn't able to solve this issue.
Any help will be really useful.
The pointer to the Singleton is static in the Utilities-Class itself, so the Singelton can never be available for GC, unless you set utils = null on leaving MainAcitivity with something like
Utilities.reset();
Your singleton wont survive your app being terminated, and what's probably happening is that you aren't actually killing the app entirely during your tests. Run adb shell am force-stop <your-app-package> from your console and see whether that still results in the issue.
With that said, if you only want your data to run once per application launch, then I would recommend you move it into the onCreate() lifecycle callback of your Application
public class App extends Application {
#Override
public void onCreate() {
super.onCreate();
//Begin process to fetch data and cache it here
}
}
Don't forget to add the application name to your manifest too, <application android:name="your.packagename.App"
You then have no need to modify your data at all - it will only run once per application launch.
If, however, what you actually want is for your data to update every time the Activity is launched, then do the same process as before but inside onCreate() of you Activity. You can also clear it in onDestroy(), if you want it to updated when your Activity is recreated:
#Override
public void onDestry() {
super.onDestroy();
Utilities.getInstance().setDataList(null);
}
Also, if your data is bound to the lifecycle of your Activity, then you don't really need a singleton (which is bound to the lifecycle of the application).
I have an List called messages property in my Activity.In the synchronization(),I called getDateMessage(upadated_at) function.In this function value of messages has changed but when program go to synchronization messages list is empty.
private List<message_model> messages = new ArrayList<>();
private void synchronization() {
getDateMessage(upadated_at);
Log.e("MSDF",messages.toString()+" list tostring");
}
private void getDateMessage(String date) {
MessengerActivity.APIInterface apiInterface = app_net.getRetrofitInstance().create(MessengerActivity.APIInterface.class);
retrofit2.Call<List<message_model>> call = apiInterface.getMessageDate(Ptoken, date);
call.enqueue(new Callback<List<message_model>>() {
#Override
public void onResponse(Call<List<message_model>> call, Response<List<message_model>> response) {
if(response.isSuccessful()) {
messages.addAll(response.body());
Log.e("MSDF",response.body().toString()+" responsebody in call");
Log.e("MSDF",messages.toString()+" message in call");
Log.e("MESSAGE", "getDateMessage successful");
}
}
#Override
public void onFailure(Call<List<message_model>> call, Throwable t) {
Log.e("MESSAGE", "getDateMessage" + t.toString());
}
});
}
And This is my logcat.
09-30 14:34:53.714 10763-10763/idea.mahdi.bime E/MSDF: [] list tostring
09-30 14:34:54.104 10763-10763/idea.mahdi.bime E/MSDF: [message_model{id=33, thread_id=2, user_id=15, body='چطوری', created_at='2018-09-29 10:28:26', updated_at='2018-09-29 10:28:26', deleted_at='null'}, message_model{id=30, thread_id=2, user_id=15, body='سلام', created_at='2018-09-29 09:30:40', updated_at='2018-09-29 09:30:40', deleted_at='null'}, message_model{id=7, thread_id=2, user_id=15, body='hi', created_at='2018-09-24 09:55:46', updated_at='2018-09-24 09:55:46', deleted_at='null'}] responsebody in api
09-30 14:34:54.104 10763-10763/idea.mahdi.bime E/MSDF: [message_model{id=33, thread_id=2, user_id=15, body='چطوری', created_at='2018-09-29 10:28:26', updated_at='2018-09-29 10:28:26', deleted_at='null'}, message_model{id=30, thread_id=2, user_id=15, body='سلام', created_at='2018-09-29 09:30:40', updated_at='2018-09-29 09:30:40', deleted_at='null'}, message_model{id=7, thread_id=2, user_id=15, body='hi', created_at='2018-09-24 09:55:46', updated_at='2018-09-24 09:55:46', deleted_at='null'}] message in api
09-30 14:34:54.104 10763-10763/idea.mahdi.bime
E/MESSAGE: getDateMessage successful
The problem is that when you call getDataMessage() it performs an asynchronous call (the retrofit enqueue() method). The server will be called to get the messages in a backgroud thread, while the android application will keep in the main thread.
Therefore, Log.e("MSDF",messages.toString()+" list tostring"); is called before the retrofit call is made, hence, there is no current data available yet. You should make sure that you are doing something with the data after it is completed loaded.
private List<message_model> messages = new ArrayList<>();
private void synchronization() {
getDateMessage(upadated_at);
// Anything you put here will be called before the data (messages) is loaded.
// Do not work with your messages here, they'll be null.
}
private void getDateMessage(String date) {
MessengerActivity.APIInterface apiInterface = app_net.getRetrofitInstance().create(MessengerActivity.APIInterface.class);
retrofit2.Call<List<message_model>> call = apiInterface.getMessageDate(Ptoken, date);
call.enqueue(new Callback<List<message_model>>() {
#Override
public void onResponse(Call<List<message_model>> call, Response<List<message_model>> response) {
if(response.isSuccessful()) {
messages.addAll(response.body());
Log.e("MSDF",response.body().toString()+" responsebody in call");
Log.e("MSDF",messages.toString()+" message in call");
Log.e("MESSAGE", "getDateMessage successful");
// Anything you want to do with the messages should be placed here. When you are sure the data is completed.
Log.e("MSDF",messages.toString()+" list tostring");
}
}
#Override
public void onFailure(Call<List<message_model>> call, Throwable t) {
Log.e("MESSAGE", "getDateMessage" + t.toString());
}
});
}
It's worth checking if (response.body() != null) before doing something with it to avoid NPE.
EDIT
As it was asked in the comments. A good solution (Google recommends it) is to fetch the data using a view model as described in this android dev guide article.
ViewModel approach is good because:
The data persist during configuration changes (for example, if you rotate your device, your list of messages will be still in your app).
It does not cause memory leaks.
You separate view data ownership from UI controller logic.
You can see the other advantages in the article.
1 - Add the view model dependecies in your build.gradle(Module:app) file
dependencies {
def lifecycle_version = "1.1.1"
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
}
See here the latest version.
2 - Create a ViewModel class
MessageViewModel.java
public class MessagesViewModel extends ViewModel {
private MutableLiveData<List<message_model>> messagesList;
public LiveData<List<message_model>> getMessages() {
if (messagesList == null) {
messagesList = new MutableLiveData<List<message_model>>();
loadMessages();
}
return messagesList;
}
private void loadMessages() {
MessengerActivity.APIInterface apiInterface = app_net.getRetrofitInstance().create(MessengerActivity.APIInterface.class);
retrofit2.Call<List<message_model>> call = apiInterface.getMessageDate(Ptoken, date);
call.enqueue(new Callback<List<message_model>>() {
#Override
public void onResponse(Call<List<message_model>> call, Response<List<message_model>> response) {
if(response.isSuccessful()) {
if (response.body() != null) {
messagesList.setValue(response.body());
}
}
}
#Override
public void onFailure(Call<List<message_model>> call, Throwable t) {
// Handle failure
}
});
}
}
3 - Get the messages in your activity
public class MainActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
MessagesViewModel model = ViewModelProviders.of(this).get(MessagesViewModel.class);
model.getMessages().observe(this, messagesList -> {
// Do whatever you want with the list of messages.
});
}
}
Look how clean your activity is now.
Then you can implement a SwipeRefreshLayout if you want to allow your users to refresh the data.
If it is not enough, you can check this ReposViewModel
Finally, if calling retrofit is the main core of your app that is going to be released to the public, you should introduce MVVM approach using Dagger 2 and RxJava, as described in this article. (This is advanced)
I am struggling in choosing the right way to pass data from broadcastReceiver to ViewModel and from there I pass data to my Repository and update LiveData. I use FCM push notifications and have local broadCastReceiver which uses ActivityLifecycle.
I found that it is bad practice to access ViewModel from BroadcastReceiver, but not sure why?
If I manage lifecycle of broadcastReceiver it should not cause any problems... So what is the best way to pass received data from FCM to my Repository's MediatorLiveData? I use MediatorLiveData, because I add different LiveData sources(API request and FCM).
Would be grateful for advice and correct way of implementing broadCastReceiver.
I have thought about accessing Repository from BroadCastReceiver, like this:
RepositoryMain.getSingletonInstance().setResponse(state);
You need to define single source of truth (SSoT). In your case it Repository (if Repository encapsulate db persistence storage, SSoT it is db). Now you need to implement data flow from receiver to view through SSoT (Repository) like in example below:
Receiver implementation
public class FcmReceiver extends BroadcastReceiver {
private final MutableLiveData<MyData> mData = new MutableLiveData<>();
public LiveData<MyData> getData() {
return mData;
}
#Override
public void onReceive(Context context, Intent intent) {
// entry point of data
mData.setValue(new MyData());
}
}
Repository
public class Repository {
private static final Repository INSTANCE = new Repository();
private final MediatorLiveData<MyData> mData = new MediatorLiveData<>();
private Repository() {}
public static Repository instance() {
return INSTANCE;
}
public LiveData<MyData> getData() {
return mData;
}
public void addDataSource(LiveData<MyData> data) {
mData.addSource(data, mData::setValue);
}
public void removeDataSource(LiveData<MyData> data) {
mData.removeSource(data);
}
}
View model
public class MyViewModel extends ViewModel {
public LiveData<MyData> getData() {
// for simplicity return data directly to view
return Repository.instance().getData();
}
}
Activity
There is binding of receiver data and Repository
public class MainActivity extends AppCompatActivity {
private FcmReceiver mReceiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReceiver = new FcmReceiver();
}
#Override
protected void onStart() {
super.onStart();
// add data source
Repository.instance().addDataSource(mReceiver.getData());
registerReceiver(mReceiver, IntentFilter.create("action", "type"));
}
#Override
protected void onStop() {
super.onStop();
// don't forget to remove receiver data source
Repository.instance().removeDataSource(mReceiver.getData());
unregisterReceiver(mReceiver);
}
}
I think you don't need to access the BroadCastReceiver within the viewmodel. Alternatively, use the BroadCastReceiver to move data between activities, if you want to do any logic to the data, send it to the viewModel related to that activity.
In Simple words:
Suppose we have the following components:
ActivityOne observes ViewModelOne
ActivityTwo observes ViewModelTwo
BroadCastReceiver [Send actions from ActivityOne]. ActivityTwo Listens to those actions
once ActivityOne receives data from the viewModelOne, it sends the data via the BroadCastReceiver.
ActivityTwo has registered for the BroadCastReceiver, thus it receives those actions, if it dose need to do any logic to the data, it can send it to the ViewModelTwo.
The problem
A presenter that "manages" a passive view subscribes to events that occur in that view (e.g. button click), and does not directly expose the methods that handle those events as public interface. I don't like the idea to make those methods public just for unit-testing since it smells like exposing the internal implementation details. Therefore, calling that event handling code becomes quite non-trivial.
My solution
The view mock has to "intercept" the event subscription and then the corresponding intercepted listener is used to call the event handling code. My implementation includes a utility class that implements the Answer interface from the Mockito API
private class ArgumentRetrievingAnswer<TArg> implements Answer {
private TArg _arg;
#Override
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
_arg = (TArg)args[0];
return null;
}
public TArg getArg() {
return _arg;
}
}
The event subscription is intercepted in the following way
XyzView xyzView = mock(XyzView.class);
ArgumentRetrievingAnswer<OnEventListener> xyzViewTouchedListenerInterceptor =
new ArgumentRetrievingAnswer<OnEventListener>();
doAnswer(xyzViewTouchedListenerInterceptor)
.when(xyzView).addViewTouchedListener(any(OnEventListener.class));
After creating the SUT instance...
XyzPresenter sut = new XyzPresenter(xyzView);
...I obtain the listener
OnEventListener xyzViewTouchListener = xyzViewTouchedListenerInterceptor.getArg();
In the "Act" part I call the event handling method of the listener
xyzViewTouchListener.onEvent();
The question
I'm quite new to unit testing in Java, so I'd like to know if there's any more elegant way of testing the presenter code. The current "Arrange" part is quite bloated an does not seem to excel in readability.
Edit:
Adding the simplified SUT code on Jonathan's request. It illustrates that the presenter does not have any public methods (except constructor), and subscribes to the view events.
public interface XyzView {
void setInfoPanelCaptionText(String text);
void addViewInitializedListener(OnEventListener listener);
void addViewTouchedListener(OnEventListener listener);
}
public class XyzPresenter {
private XyzView _xyzView;
private OnEventListener _xyzViewTouchedListener = new OnEventListener() {
#Override
public void onEvent() {
handleXyzViewTouch();
}
};
public XyzPresenter(XyzView xyzView) {
_xyzView = xyzView;
_xyzView.addViewTouchedListener(_xyzViewTouchedListener);
}
private void handleXyzViewTouch() {
// event handling code
}
}
Basically I also use ArgumentCaptor in this setup.
The basic layout of my presenter tests is like this :
#RunWith(MockitoJUnitRunner.class)
public class PresenterTest {
private Presenter sut;
#Mock
private View view;
#Captor
private ArgumentCaptor<ViewTouchedListener> listenerCaptor;
private ViewTouchedListener listener;
#Before
public void setUp() {
sut = new Presenter(view);
verify(view).addViewTouchedListener(listenerCaptor.capture());
listener = listenerCaptor.getValue();
}
// test methods have access to both sut and its registered listener
}
Thanks to #Jonathan for suggesting ArgumentCaptor, I can use it instead of my "re-invented wheel" ArgumentRetrievingAnswer. I managed to stub void methods for event subscribing to use ArgumentCaptor, although it has some after-taste of a hack.
ArgumentCaptor<OnEventListener> xyzViewTouchedListenerCaptor =
ArgumentCaptor.forClass(OnEventListener.class);
doNothing().when(xyzView).addViewTouchedListener(xyzViewTouchedListenerCaptor.capture());
I have a simple application and want to make it testable. I m new in this area.
Here is a simple Presenter, taking in mind this code ,could you advice or give me some example how to test it.
public class SomePresenter extends Presenter<MainPanelPresenter.Display>
{
public interface Display extends WidgetDisplay
{
HasClickHandlers getAddButton();
HasClickHandlers getDeleteButton();
void setData(ArrayList<Person> data);
ArrayList<String> getSelectedRows();
Widget asWidget();
}
private final DispatchAsync dispatcher;
public static final Place PLACE = new Place("main");
#Inject
public SomePresenter(DispatchAsync dispatcher, EventBus eventBus, Display display)
{
super(display, eventBus);
this.dispatcher = dispatcher;
bind();
}
protected void onBind()
{
display.getAddButton().addClickHandler(new ClickHandler()
{
public void onClick(ClickEvent event)
{
eventBus.fireEvent(new AddButtonEvent());
}
});
display.getDeleteButton().addClickHandler(new ClickHandler()
{
public void onClick(ClickEvent event)
{
ArrayList<String> list = display.getSelectedRows();
deletePerson(list);
}
});
}
....
private void loadDbData()
{
..........
}
private void deletePerson(ArrayList<String> ids)
{
..........
}
}
Edit:
What does the Presenter is, load initial data from db, have 2 buttons add and delete.
When add is press then a new form is load and user is able to input data and save to the db,
delete button just delete person from db.
Thanks
The general idea of unit testing such a class would be, like for any other class :
create Mock version of the dependencies (Display, EventBus, etc...)
set expectations on what the depdencies should do when the Presenter works
exercice the Presenter and check the expectations
However there are a couple of issues with your version of the Presenter :
The loadDbData() method is not showed, but I assumed it means the Presenter also has access to some other component that does the fetching. Can this component be abtracted in a dependency, and mocked liked the rest ?
Then there is the testing of bind(). The only responsibility of your Presenter in this method is to set up callbacks on some buttons provided by the Display. What you want to test is both :
That the callbacks are set
That the set callbacks do the expected things
A few ideas to help with the later :
You can reduce the coupling between Presenter and Button. If possible, change the Display interface from :
Button getAddButton();
to
addAddButtonClickedHandler(ClickHandler);
This means your Presenter does not have to use a Display object that returns actual BUtton
You can reduce the callbacks content to calling a single method, that you can then test in isolation
protected void bind() {
display.addAddButtonClickHandler(new ClickHandler() {
public void onClick(ClickEvent) {
fireAdded();
}
});
}
// The fireAdded function can be tested independenty of the Display, potentially with
// a mock EventBus
protected void fireAdded() {
event.fireEvent(....)
}
If you really want to check that the callbacks are properly set, than you can use a 'Dummy' implementation of the Display class, that provides you a list of all the callbacks, and let you call them
private class DummyDisplay implements Display {
private List<ClickHandler> addButtonClickHandlers;
public void addAddButtonClickHandler(ClickHandler handler) {
addButtonClickHandlers.add(handler);
}
public void fireAddButtonClick() {
for (ClickHandler h in addButtonClickHandlers) {
h.onClick(new ClickEvent());
}
}
// ....
}
Then your test would :
create a presenter with such a dummy display
use bind to set the callbacks
use display.fireAddButtonClick() to simulate a user clicking
check that has the result of the click, the effects of fireAdded are seen
This type of class (that mostly glue other classes together) can tend to be hard to test ; at some point, it the other classes are thoroughly tested it can become slightly counter productive to concentrate on the gluers, rather than the glued.
Hoping this helps.