I have an Activity1 with a TabLayout of two Fragments (each of them with a presenter). Once I click a button on the Toolbar a new Activity2 is started (with startActivityWithResults) which contains a simple list. At the selection of one of the items in the list the Activity2 returns the selected string to the previous Activity1 (the one with the TabLayout).
Now, once onActivityResult is called in Activity1, this one will call an API (using a presenter) that will get the new results and then it should update the two fragments in the TabLayout. I'm thinking to do it with RxJava but I have no idea where to start from.
The Activity1:
public class Activity1 extends BaseActivity {
#Inject
Actvity1Presenter mPresenter;
public static Intent newIntent(Context packageContext) {
return new Intent(packageContext, Activity1.class);
}
#LayoutRes
protected int getLayoutRedIs() {
return R.layout.app_bar_activity1;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutRedIs());
FwApplication.component(this).inject(this);
mPresenter.attachView(this);
Toolbar tb = (Toolbar) findViewById(R.id.toolbar_chips);
setSupportActionBar(tb);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_back_arrow);
mTabLayout = (TabLayout) findViewById(R.id.tab_layout);
mTabLayout.addTab(mTabLayout.newTab().setText("TAB1"));
mTabLayout.addTab(mTabLayout.newTab().setText("TAB2"));
mTabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
mViewPager = (ViewPager) findViewById(R.id.viewpager);
mViewPager.setAdapter(new PagerAdapter(getSupportFragmentManager(),
mTabLayout.getTabCount()));
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
mViewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PICK_ITEM_CODE) {
if (resultCode == RESULT_OK) {
mPresenter.updateResults(data);
}
if (resultCode == RESULT_CANCELED) {
}
}
}
And the pager:
public class PagerAdapter extends FragmentPagerAdapter {
int mNumOfTabs;
public PagerAdapter(FragmentManager fm, int NumOfTabs) {
super(fm);
this.mNumOfTabs = NumOfTabs;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return Fragment1.newInstance();
break;
case 1:
return Fragment2.newInstance();
break;
}
}
#Override
public int getCount() {
return mNumOfTabs;
}
}
EDIT
ActivityPresenter:
public class ActivityPresenter implements Presenter<ActivityView>,
Interactor.OnFinishedListener<Response> {
private static final String TAG = "FW.ActivityPresenter";
#Inject
QueryPreferences mQueryPreferences;
private Interactor mInteractor;
private ActivityView mView;
private NetworkService mNetworkService;
private boolean mUseCache;
private String mQuery;
private int mPage;
private PublishSubject<Response> mPublishSubject = PublishSubject.create();
Observable<Response> getObservableResults() {
return mPublishSubject;
}
#Inject
public ActivityPresenter(NetworkService networkService) {
mNetworkService = networkService;
mInteractor = new InteractorImpl(mNetworkService);
}
public void onSearchQueryListener(String query, int page) {
mQuery = mQueryPreferences.getStoredQuery();
mUseCache = query.equals(mQuery);
if (!mUseCache) {
mQueryPreferences.setStoredQuery(query);
Log.d(TAG, "Query added to cache: " + query);
}
mPage = page;
mInteractor.loadResults(this, query, false, page);
}
#Override
public void onFinished(Response response) {
if (mView != null) {
mPublishSubject.onNext(response);
}
}
#Override
public void onError(Throwable throwable) {
if (mView != null) {
mView.showMessage(throwable.getMessage());
}
}
#Override
public void attachView(ActivityView mvpView) {
mView = mvpView;
}
#Override
public void detachView() {
mView = null;
mInteractor.unSubscribe();
}
}
InteractorImpl:
public class InteractorImpl implements Interactor {
private static final String TAG = "FW.InteractorImpl";
private NetworkService mNetworkService;
private Subscription mSubscription;
public InteractorImpl(NetworkService networkService) {
mNetworkService = networkService;
}
#Override
public void loadResults(final OnFinishedListener listener, String query, boolean useCache, int page) {
Observable<Response> responseObservable = (Observable<Response>)
mNetworkService.getObservable(mNetworkService.getAPI().getResponseObservable(query, page), Response.class, true, useCache);
mSubscription = responseObservable.subscribe(new Observer<Response>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Log.e(TAG, e.getMessage());
listener.onError(e);
}
#Override
public void onNext(Response response) {
listener.onFinished(response);
}
});
}
public void unSubscribe() {
if(mSubscription != null && !mSubscription.isUnsubscribed()) {
mSubscription.unsubscribe();
}
}
}
FragmentPresenter:
public class FragmentPresenter implements Presenter<FragmentView>,
Interactor.OnFinishedListener<Response> {
private static final String TAG = "FW.FragmentPres";
#Inject
QueryPreferences mQueryPreferences;
private Interactor mInteractor;
private FragmentView mView;
private NetworkService mNetworkService;
private ActivityPresenter mActvityPresenter;
#Inject
public FragmentPresenter(NetworkService networkService) {
mNetworkService = networkService;
mInteractor = new InteractorImpl(mNetworkService);
}
void attachRecipeActivityPresenter(ActivityPresenter activityPresenter) {
mActvityPresenter = activityPresenter;
mActvityPresenter.getObservableResults().subscribe(data -> showData(data));
}
private void showData(Response response) {
if (response.getResults().getModels().isEmpty() && mPage == 0) {
mView.showNoResults();
} else {
mView.showResults(response.getResults().getModels());
}
}
#Override
public void onError(Throwable throwable) {
if (mView != null) {
mView.hideProgressBar();
mView.showMessage(throwable.getMessage());
}
}
#Override
public void attachView(FragmentView mvpView) {
mView = mvpView;
}
#Override
public void detachView() {
mView = null;
mInteractor.unSubscribe();
}
}
Using Retrofit2 and RxAndroid your method will look like this:
public void updateResults(String data) {
yourRetrofitAPI.getSomething(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(() -> {
// show your progress dialog
})
.subscribe(result -> {
// pass result to your view
}, error -> {
// hide your progress dialog
// get error message and send to your view
}, () -> {
// hide your progress dialog
});
}
interface YourRetrofitAPI {
#GET("/yourResource/{data}")
Observable<String> getSomething(#Path("data") String data);
}
So, about notify your fragments, with MVP you can make presenter fragments observe a stream from activity presenter, so both fragments will be notified when you query ends.
public class ExampleUnitTest {
#Test
public void testSample() throws Exception {
ActivityPresenter activityPresenter = new ActivityPresenter();
Fragment1Presenter fragment1Presenter = new Fragment1Presenter();
Fragment2Presenter fragment2Presenter = new Fragment2Presenter();
fragment1Presenter.attachActivityPresenter(activityPresenter);
fragment2Presenter.attachActivityPresenter(activityPresenter);
Observable.range(1, 10)
.delay(2, TimeUnit.SECONDS, Schedulers.immediate())
.subscribe(integer -> activityPresenter.queryData("query: " + integer));
}
class ActivityPresenter {
PublishSubject<String> publishSubject = PublishSubject.create();
Observable<String> serverDataAsObservable() {
return publishSubject.map(s -> String.format("%d - %s", System.currentTimeMillis(), s));
}
void queryData(String input) {
// based on your input you should query data from server
// and then emit those data with publish subject
// then all subscribers will receive changes
publishSubject.onNext(input);
}
}
class Fragment1Presenter {
private ActivityPresenter activityPresenter;
void attachActivityPresenter(ActivityPresenter activityPresenter) {
this.activityPresenter = activityPresenter;
this.activityPresenter.serverDataAsObservable()
.subscribe(data -> showData(data));
}
private void showData(String data) {
System.out.println("showing data on fragment1 with " + data);
}
}
class Fragment2Presenter {
private ActivityPresenter activityPresenter;
void attachActivityPresenter(ActivityPresenter activityPresenter) {
this.activityPresenter = activityPresenter;
this.activityPresenter.serverDataAsObservable()
.subscribe(data -> showData(data));
}
private void showData(String data) {
System.out.println("showing data on fragment2 with " + data);
}
}
}
Hope that it helps.
Best regards.
Related
I have the same UI-related features and implementations that I want to use once in Activity and again in Fragment. How do I implement the architecture to avoid duplicate code?
Does displaying a Fragment in an Activity reduce performance?
My goal is to implement an abstract class for other classes to use pagination. Child classes are sometimes Activity type and sometimes Fragment type.
I designed an interface that has the following methods. Then I implemented these features in the abstract Fragment class:
public interface PaginableComponent {
void onRequestList();
void onSuccessLoadList(#NonNull List<Paginable> list);
void onErrorLoadList(String errorMessage);
void onRequestLoadMoreList(int oldTotalCount);
void onSuccessLoadMore(#NonNull List<Paginable> list);
void onTryLoadList();
#NonNull
PaginableAdapter getAdapter();
}
This is the abstract Fragment class that implements this interface:
public abstract class PaginableFragment extends BaseFragment
implements PaginableComponent, PaginableAdapter.LoadListMoreListener
{
private final AtomicBoolean mIsFirstLoadedList = new
AtomicBoolean(false);
private SwipeRefreshLayout mSwipeRefreshLayout;
private View mStatusLayout;
private ProgressBar mStatusProgressBar;
private TextView mStatusMessageTextView;
private TextView mTryAgainTextView;
public Boolean isHidden = true;
#CallSuper
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initViews(view);
getAdapter().setLoadListMoreListener(this);
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setColorSchemeColors(UiUtils.getStyleColor(requireContext(), R.attr.colorPrimary));
mSwipeRefreshLayout.setOnRefreshListener(this::checkNetAndRequestList);
}
}
#Override
public void onPause() {
super.onPause();
isHidden = true;
}
#Override
public void onResume() {
super.onResume();
isHidden = isHidden();
}
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
isHidden = hidden;
}
private void initViews(View view) {
mSwipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
mStatusLayout = view.findViewById(R.id.statusLayout);
if (mStatusLayout != null) {
mStatusProgressBar = mStatusLayout.findViewById(R.id.progressBar);
mStatusMessageTextView = mStatusLayout.findViewById(R.id.text);
mTryAgainTextView = mStatusLayout.findViewById(R.id.tryAgain);
mTryAgainTextView.setOnClickListener(v -> onTryLoadList());
}
}
protected void showStatus(String type) {
if (mStatusLayout != null) {
switch (type) {
case Messages.MSG_LOADING:
mStatusProgressBar.setVisibility(View.VISIBLE);
mStatusMessageTextView.setVisibility(View.GONE);
mTryAgainTextView.setVisibility(View.GONE);
break;
case Messages.MSG_NET:
mStatusMessageTextView.setText(R.string.no_network_connection);
mStatusProgressBar.setVisibility(View.GONE);
mStatusMessageTextView.setVisibility(View.VISIBLE);
mTryAgainTextView.setVisibility(View.VISIBLE);
break;
case Messages.MSG_ERROR:
mStatusMessageTextView.setText(R.string.sorry_unexpected_error_occurred);
mStatusProgressBar.setVisibility(View.GONE);
mStatusMessageTextView.setVisibility(View.VISIBLE);
mTryAgainTextView.setVisibility(View.GONE);
break;
}
mStatusLayout.setVisibility(View.VISIBLE);
}
}
private void hideStatus() {
if (mStatusLayout != null) {
mStatusLayout.setVisibility(View.GONE);
}
}
#CallSuper
#Override
public void onSuccessLoadList(#NonNull List<Paginable> list) {
mIsFirstLoadedList.set(true);
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(false);
}
hideStatus();
getAdapter().resetList(list);
}
#CallSuper
#Override
public void onErrorLoadList(String errorMessage) {
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(false);
}
showStatus(Messages.MSG_ERROR);
getAdapter().setIsLoadingMore(false);
}
#CallSuper
#Override
public void onLoadListMore(int oldTotalCount) {
getAdapter().showEndMessage(Paginable.TYPE_SHIMMER);
if (NetReceiver.isNetConnected(requireContext())) {
onRequestLoadMoreList(oldTotalCount);
} else {
showStatus(Messages.MSG_NET);
}
}
#CallSuper
#Override
public void onSuccessLoadMore(#NonNull List<Paginable> list) {
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(false);
}
hideStatus();
getAdapter().hideEndMessages();
getAdapter().addList(list);
getAdapter().setIsLoadingMore(false);
}
#CallSuper
#Override
public void onTryLoadList() {
if (!mIsFirstLoadedList.get()) {
checkNetAndRequestList();
}
}
#Override
public void onNetEvent(NetReceiver.NetEvent event) {
if (event.isConnected()) {
hideStatus();
if (!mIsFirstLoadedList.get()) {
getAdapter().clearAndInitShimmers();
onRequestList();
}
if (getAdapter().isLoadingMore()) {
onRequestLoadMoreList(getAdapter().getItemCount());
}
} else {
showStatus(Messages.MSG_NET);
}
}
public void checkNetAndRequestList() {
if (NetReceiver.isNetConnected(requireContext())) {
if (!mIsFirstLoadedList.get()) {
getAdapter().clearAndInitShimmers();
}
onRequestList();
} else {
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(false);
}
showStatus(Messages.MSG_NET);
if (!mIsFirstLoadedList.get()) {
getAdapter().clearAndInitNoInternet();
}
}
}
}
Now I want to use the same implementation in the activities.
One way to prevent duplicate code is to use ViewModel and LiveData in the MVVM architecture.
I have some classes as presenter and in these classes I use retrofit for some methods. But some methods are duplicated. So I want to use a class for all retrofit and connect to server methods and call them when I want.
But when I created that class it has NullpointerException Error
I will be very thankful if you help me
this is presenter codes:
public class DefinitionPresenter implements DefinitionContract.Presenter {
private KalaBeanDataSource kalaBeanDataSource;
private DefinitionContract.View view;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private DatabaseMethods databaseMethods;
private ActivityKindList activityKindList;
public DefinitionPresenter(KalaBeanDataSource kalaBeanDataSource){
this.kalaBeanDataSource = kalaBeanDataSource;
databaseMethods = new DatabaseMethods(kalaBeanDataSource,compositeDisposable);
activityKindList = new ActivityKindList();
}
#Override
public void attachView(DefinitionContract.View view) {
this.view = view;
}
#Override
public void detachView() {
view = null;
if(compositeDisposable != null && compositeDisposable.size() > 0){
compositeDisposable.clear();
}
}
#Override
public void activityKind() {
activityKindList = databaseMethods.getActivityKind();
if(activityKindList.getItems().size() > 0){
view.getActivityKind(activityKindList);
}else{
view.showMessage(databaseMethods.message);
}
}
}
And this is a class that I created for get data from server with retrofit and RxJava
public class DatabaseMethods {
private KalaBeanDataSource kalaBeanDataSource;
private CompositeDisposable compositeDisposable;
private ActivityKindList activityKindListResult;
public String message = null;
public DatabaseMethods(KalaBeanDataSource kalaBeanDataSource,CompositeDisposable compositeDisposable){
this.kalaBeanDataSource = kalaBeanDataSource;
this.compositeDisposable = compositeDisposable;
activityKindListResult = new ActivityKindList();
}
public ActivityKindList getActivityKind(){
kalaBeanDataSource.getActivityKind().subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<ActivityKindList>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onSuccess(ActivityKindList activityKindList) {
activityKindListResult = activityKindList;
}
#Override
public void onError(Throwable e) {
message = e.toString();
}
});
if(message == null && activityKindListResult.getItems().size() > 0){
return activityKindListResult;
}else{
return null;
}
}
this method always returns null:
public ActivityKindList getActivityKind(){
kalaBeanDataSource.getActivityKind().subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<ActivityKindList>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onSuccess(ActivityKindList activityKindList) {
activityKindListResult = activityKindList;
}
#Override
public void onError(Throwable e) {
message = e.toString();
}
});
if(message == null && activityKindListResult.getItems().size() > 0){
return activityKindListResult;
}else{
return null;
}
}
1) make this method void
2) create an interface and call it in onSuccess() and onError()
3) implement interface in your presenter
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
This is my first attempt at working with the Google Lifecycle Architecture component with Fragments. My app uses MVP with a BaseViewModel for persisting the Presenter across configuration changes.
In my Activities, all Activity lifecycle events are called for the Presenter as expected courtesy of the Lifecycle component; however, none of the Fragment Presenters' lifecycle events are called.
Code Time!
BaseViewModel.java
public final class BaseViewModel<V extends BaseContract.View, P extends BaseContract.Presenter<V>>
extends ViewModel {
private P presenter;
private static final String TAG = "[" + BaseViewModel.class.getSimpleName() + "] ";
void setPresenter(P presenter) {
Log.d(APP_TAG, TAG + "setPresenter presenter: " + presenter);
if (this.presenter == null) {
this.presenter = presenter;
}
}
P getPresenter() {
Log.d(APP_TAG, TAG + "getPresenter presenter: " + presenter);
return this.presenter;
}
#Override
protected void onCleared() {
Log.d(APP_TAG, TAG + "onCleared ");
super.onCleared();
presenter.onPresenterDestroy();
presenter = null;
}
}
BaseFragment.java
public abstract class BaseFragment<V extends BaseContract.View, R extends BaseContract.Repository, P extends BaseContract.Presenter<V>>
extends Fragment implements BaseContract.View {
private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
protected P presenter;
#SuppressWarnings("unchecked")
#CallSuper
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//Initializes the ViewModel
BaseViewModel<V, P> viewModel = ViewModelProviders.of(this).get(BaseViewModel.class);
boolean isPresenterCreated = false;
if (viewModel.getPresenter() == null) {
viewModel.setPresenter(initPresenter());
isPresenterCreated = true;
}
presenter = viewModel.getPresenter();
presenter.attachLifecycle(getLifecycle());
presenter.attachView((V) this);
presenter.setRepo(initRepository());
if (isPresenterCreated) {
presenter.onPresenterCreated();
} else {
//Hacky workaround I don't want to use
presenter.onPresenterRestored();
}
}
#Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistry;
}
#CallSuper
#Override
public void onDestroyView() {
super.onDestroyView();
presenter.detachLifecycle(getLifecycle());
presenter.detachView();
}
protected abstract P initPresenter();
protected abstract R initRepository();
}
BasePresenter.java
public abstract class BasePresenter<V extends BaseContract.View> implements LifecycleObserver, BaseContract.Presenter<V> {
private Bundle stateBundle;
private V view;
private BaseContract.Repository repo;
#Override
final public V getView() {
return view;
}
#SuppressWarnings("unchecked")
#Override
final public BaseContract.Repository getRepo() {
return repo;
}
#Override
final public void attachLifecycle(Lifecycle lifecycle) {
lifecycle.addObserver(this);
}
#Override
final public void detachLifecycle(Lifecycle lifecycle) {
lifecycle.removeObserver(this);
}
#Override
final public <R extends BaseContract.Repository> void setRepo(R repo) {
this.repo = repo;
}
#Override
final public void attachView(V view) {
this.view = view;
}
#Override
final public void detachView() {
view = null;
}
#Override
final public boolean isViewAttached() {
return view != null;
}
#Override
final public Bundle getStateBundle() {
return stateBundle == null
? stateBundle = new Bundle()
: stateBundle;
}
#CallSuper
#Override
public void onPresenterDestroy() {
if (stateBundle != null && !stateBundle.isEmpty()) {
stateBundle.clear();
}
}
#Override
public void onPresenterCreated() {
Log.d(APP_TAG, "BasePresenter.OnPresenterCreated");
}
#Override
public void onPresenterRestored() {
//Hacky
Log.d(APP_TAG, "BasePresenter.onPresenterRestored");
}
#OnLifecycleEvent(value = Lifecycle.Event.ON_START)
void onStart() {
if (isViewAttached()) {
onStartWithAttachedView();
}
}
public void onStartWithAttachedView() {
}
}
The Fragment I am testing:
GameSituationsFragment.java
public class GameSituationsFragment extends BaseFragment<GameSituationsContract.View,
GameSituationsContract.Repository, GameSituationsContract.Presenter>
implements GameSituationsContract.View {
#BindView(R.id.gs_prev_session_button)
ImageView prevSessButton;
#BindView(R.id.gs_next_session_button)
ImageView nextSessButton;
#BindView(R.id.gs_selected_session)
TextView selectedSessionText;
private static final String TAG = "[" + GameSituationsFragment.class.getSimpleName() + "] ";
private InteractionListener interactionListener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) return;
if (activity instanceof GameSituationsFragment.InteractionListener) {
interactionListener = (GameSituationsFragment.InteractionListener) activity;
} else {
if (BuildConfig.DEBUG) {
throw new RuntimeException(activity.toString()
+ " must implement GameSituationsFragment.InteractionListener");
} else {
//Todo what do we want to do?
Log.e(TAG, activity.toString() + " must implement GameSituationsFragment.InteractionListener");
}
}
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof GameSituationsFragment.InteractionListener) {
interactionListener = (GameSituationsFragment.InteractionListener) context;
} else {
if (BuildConfig.DEBUG) {
throw new RuntimeException(context.toString()
+ " must implement GameSituationsFragment.InteractionListener");
} else {
//Todo what do we want to do?
Log.e(TAG, context.toString() + " must implement GameSituationsFragment.InteractionListener");
}
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_game_situations, container, false);
ButterKnife.bind(this, view);
return view;
}
#Override
protected GameSituationsContract.Presenter initPresenter() {
return new GameSituationsPresenter();
}
#Override
protected GameSituationsContract.Repository initRepository() {
return new GameSituationsRepository();
}
public interface InteractionListener{
void onGsNextSessionClicked();
void onGsPreviousSessionClicked();
}
}
And finally the Fragment's Presenter:
GameSituationsPresenter.java
public class GameSituationsPresenter extends BasePresenter<GameSituationsContract.View> implements GameSituationsContract.Presenter{
GameSituationsRepository repo;
private Bundle viewStateBundle = getStateBundle();
private static final String TAG = "[" + GameSituationsPresenter.class.getSimpleName() + "] ";
#Override
public void onPresenterCreated(){
Log.d(APP_TAG, TAG + "onPresenterCreated");
repo = (GameSituationsRepository) getRepo();
...
initializeViews();
}
#Override
public void onPresenterRestored() {
//So hacky
initializeViews();
}
//NOT CALLED
#OnLifecycleEvent(value = Lifecycle.Event.ON_START)
protected void onStart(){
Log.d(APP_TAG, TAG + "onStart");
if (isViewAttached()) {
}
}
//NOT CALLED
#OnLifecycleEvent(value = Lifecycle.Event.ON_STOP)
protected void onStop(){
if (isViewAttached()) {
}
}
//NOT CALLED
#OnLifecycleEvent(value = Lifecycle.Event.ON_DESTROY)
protected void onDestroy() {
if (isViewAttached()) {
//Something to do?
}
//a little cleanup
repo = null;
}
private void initializeViews(){
//Do view stuff
}
}
So the question is are fragments able to receive Lifecycle Events or did I miss something in my implementation?
Thanks in advance.
I'm new to Espresso testing. In my existing application we are using RxAndroid to do some networking. We use a RxBus to communicate to parts of our application that would otherwise seem "impossible".
We imported RxEspresso which implements IdlingResource so we could use our RxAndroid network calls.
Unfortunately RxEspresso does not allow RxBus to work since it's a "hot observable" and never closes. So it throws android.support.test.espresso.IdlingResourceTimeoutException: Wait for [RxIdlingResource] to become idle timed out
I made a small Android application demonstrating my point.
It has two activities. The first displays some items retrieved through a network call on startup in a RecyclerView.
When clicked upon it communicates through the RxBus (I know it's overkill, but purely to demonstrate the point). The DetailActivity then shows the data.
How can we edit RxEspresso so it will work with our RxBus?
RxIdlingResource also check RxEspresso
/**
* Provides the hooks for both RxJava and Espresso so that Espresso knows when to wait
* until RxJava subscriptions have completed.
*/
public final class RxIdlingResource extends RxJavaObservableExecutionHook implements IdlingResource {
public static final String TAG = "RxIdlingResource";
static LogLevel LOG_LEVEL = NONE;
private final AtomicInteger subscriptions = new AtomicInteger(0);
private static RxIdlingResource INSTANCE;
private ResourceCallback resourceCallback;
private RxIdlingResource() {
//private
}
public static RxIdlingResource get() {
if (INSTANCE == null) {
INSTANCE = new RxIdlingResource();
Espresso.registerIdlingResources(INSTANCE);
}
return INSTANCE;
}
/* ======================== */
/* IdlingResource Overrides */
/* ======================== */
#Override
public String getName() {
return TAG;
}
#Override
public boolean isIdleNow() {
int activeSubscriptionCount = subscriptions.get();
boolean isIdle = activeSubscriptionCount == 0;
if (LOG_LEVEL.atOrAbove(DEBUG)) {
Log.d(TAG, "activeSubscriptionCount: " + activeSubscriptionCount);
Log.d(TAG, "isIdleNow: " + isIdle);
}
return isIdle;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
if (LOG_LEVEL.atOrAbove(DEBUG)) {
Log.d(TAG, "registerIdleTransitionCallback");
}
this.resourceCallback = resourceCallback;
}
/* ======================================= */
/* RxJavaObservableExecutionHook Overrides */
/* ======================================= */
#Override
public <T> Observable.OnSubscribe<T> onSubscribeStart(Observable<? extends T> observableInstance,
final Observable.OnSubscribe<T> onSubscribe) {
int activeSubscriptionCount = subscriptions.incrementAndGet();
if (LOG_LEVEL.atOrAbove(DEBUG)) {
if (LOG_LEVEL.atOrAbove(VERBOSE)) {
Log.d(TAG, onSubscribe + " - onSubscribeStart: " + activeSubscriptionCount, new Throwable());
} else {
Log.d(TAG, onSubscribe + " - onSubscribeStart: " + activeSubscriptionCount);
}
}
onSubscribe.call(new Subscriber<T>() {
#Override
public void onCompleted() {
onFinally(onSubscribe, "onCompleted");
}
#Override
public void onError(Throwable e) {
onFinally(onSubscribe, "onError");
}
#Override
public void onNext(T t) {
//nothing
}
});
return onSubscribe;
}
private <T> void onFinally(Observable.OnSubscribe<T> onSubscribe, final String finalizeCaller) {
int activeSubscriptionCount = subscriptions.decrementAndGet();
if (LOG_LEVEL.atOrAbove(DEBUG)) {
Log.d(TAG, onSubscribe + " - " + finalizeCaller + ": " + activeSubscriptionCount);
}
if (activeSubscriptionCount == 0) {
Log.d(TAG, "onTransitionToIdle");
resourceCallback.onTransitionToIdle();
}
}
}
RxBus
public class RxBus {
//private final PublishSubject<Object> _bus = PublishSubject.create();
// If multiple threads are going to emit events to this
// then it must be made thread-safe like this instead
private final Subject<Object, Object> _bus = new SerializedSubject<>(PublishSubject.create());
public void send(Object o) {
_bus.onNext(o);
}
public Observable<Object> toObserverable() {
return _bus;
}
public boolean hasObservers() {
return _bus.hasObservers();
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
#Bind(R.id.rv)
RecyclerView RV;
private List<NewsItem> newsItems;
private RecyclerViewAdapter adapter;
private Observable<List<NewsItem>> newsItemsObservable;
private CompositeSubscription subscriptions = new CompositeSubscription();
private RxBus rxBus;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
//Subscribe to RxBus
rxBus = new RxBus();
subscriptions.add(rxBus.toObserverable()
.subscribe(new Action1<Object>() {
#Override
public void call(Object event) {
//2.
NewsItem myClickNewsItem = (NewsItem) event;
startActivity(new Intent(MainActivity.this, DetailActivity.class).putExtra("text", myClickNewsItem.getBodyText()));
}
}));
//Set the adapter
adapter = new RecyclerViewAdapter(this);
//Set onClickListener on the list
ItemClickSupport.addTo(RV).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
#Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
//Send the clicked item over the RxBus.
//Receives it in 2.
rxBus.send(newsItems.get(position));
}
});
RV.setLayoutManager(new LinearLayoutManager(this));
RestAdapter retrofit = new RestAdapter.Builder()
.setEndpoint("http://URL.com/json")
.build();
ServiceAPI api = retrofit.create(ServiceAPI.class);
newsItemsObservable = api.listNewsItems(); //onComplete goes to setNewsItems
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
NewsItemObserver observer = new NewsItemObserver(this);
newsItemsObservable.delaySubscription(1, TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(observer);
}
public void setNewsItems(List<NewsItem> newsItems) {
this.newsItems = newsItems;
adapter.setNewsItems(newsItems);
RV.setAdapter(adapter);
}
Since we didn't obtain any better answer to this problem we assumed objects send through the RxBus were immediate and didn't need to be counted in the subscriptions.incrementAndGet();
We simply filtered the objects out before this line. In our case the objects were of the class SerializedSubject and PublishSubject.
Here is the method we changed.
#Override
public <T> Observable.OnSubscribe<T> onSubscribeStart(Observable<? extends T> observableInstance, final Observable.OnSubscribe<T> onSubscribe) {
int activeSubscriptionCount = 0;
if (observableInstance instanceof SerializedSubject || observableInstance instanceof PublishSubject) {
Log.d(TAG, "Observable we won't register: " + observableInstance.toString());
} else {
activeSubscriptionCount = subscriptions.incrementAndGet();
}
if (LOG_LEVEL.atOrAbove(DEBUG)) {
if (LOG_LEVEL.atOrAbove(VERBOSE)) {
Log.d(TAG, onSubscribe + " - onSubscribeStart: " + activeSubscriptionCount, new Throwable());
} else {
Log.d(TAG, onSubscribe + " - onSubscribeStart: " + activeSubscriptionCount);
}
}
onSubscribe.call(new Subscriber<T>() {
#Override
public void onCompleted() {
onFinally(onSubscribe, "onCompleted");
}
#Override
public void onError(Throwable e) {
onFinally(onSubscribe, "onError");
}
#Override
public void onNext(T t) {
Log.d(TAG, "onNext:: " + t.toString());
//nothing
}
});
return onSubscribe;
}