Google Architecture Lifecycle Component Not Calling Fragment Lifecycle Events - android

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.

Related

Activities and fragments with the same features

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.

Xposed - I can print inner class methods, but method hooking doesn't work

I'm trying to hook a method in inner class, but nothing happen, while I can print all the methods of that class.
All of the logs printed except which is in the replaceHookedMethod.
public class Keyguard implements IXposedHookLoadPackage {
#Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.android.keyguard"))
return;
XposedBridge.log("we are in keyguard!");
Class HwClockView = XposedHelpers.findClass("com.android.keyguard.AbsClockView$HwClockView",
lpparam.classLoader);
for (Method m : HwClockView.getDeclaredMethods()) {
XposedBridge.log("method: " + m.getName());
}
XposedHelpers.findAndHookMethod(HwClockView, "getDateString",
TimeZone.class, new XC_MethodReplacement() {
#Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("we are in getDateString!");
return String.format("%s", Utils.getPersianDateShort());
}
});
}
Update:
After second comment changed code to this, but same as before nothing happens:
public class Keyguard implements IXposedHookLoadPackage {
#Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
if (!lpparam.packageName.equals("com.android.keyguard"))
return;
XposedBridge.log("we are in keyguard!");
Class HwClockView = XposedHelpers.findClass("com.android.keyguard.AbsClockView$HwClockView",
lpparam.classLoader);
for (Method m : HwClockView.getDeclaredMethods()) {
XposedBridge.log("method: " + m.getName());
if ("getDateString".equalsIgnoreCase(m.getName())) {
XposedBridge.hookMethod(m, new XC_MethodReplacement() {
#Override
protected Object replaceHookedMethod(MethodHookParam param) {
XposedBridge.log("we are in getDateString!");
return String.format("%s", Utils.getPersianDateShort());
}
});
}
}
}
Target class:
public class AbsClockView extends RelativeLayout {
protected Calendar mCalendar;
private HwCustKeyguardStatusViewEx mCustKeyguardStatusViewEx;
protected TextView mDateView;
protected TextView mDescriptionView;
protected Factory mFactory;
protected final AtomicBoolean mFixedTimeZone;
protected FrameLayout mTimeParent;
protected TextView mTimeView;
public interface Factory {
void refreshDate();
void setHwDateFormat();
void updateHwTimeStyle();
}
private class HwClockView implements Factory {
protected Context mContext;
public HwClockView(Context context) {
...
}
private CharSequence getDateString(TimeZone timeZone) {
return someString;
}
}
public AbsClockView(Context context) {
this(context, null);
}
}

Espresso's IdlingResource with Retrofit 2

I am new to Espresso, and trying to write a test on a fragment that makes a Retrofit call when it is instantiated. When the call is received, I want to check the fragment to see if a view exists. I'm using an IdlingResource for the fragment and setting up a listener to be called that transitions the ResourceCallback to idle when the response is received (followed some steps in the implementation here: https://stackoverflow.com/a/30820189/4102823).
My fragment is instantiated when the user logs in and starts up MainActivity. The problem is that I just don't think my IdlingResource is set up correctly, and I don't know what's wrong with it. It is not even constructed until after the fragment is initiated and the call is made, even though I'm registering the IdlingResource in the test's setUp() method before everything else. So, I think the main issue here is how I get the IdlingResource instantiated alongside the fragment when I run the test, not after it. Could the problem be in the #Rule? Does it start MainActivity (which creates a NewsfeedFragment instance) before the test can run on the fragment? If so, how would I use a rule with my fragment instead?
Here is my fragment:
public ProgressListener mProgressListener;
public boolean mIsProgressShown = true;
public interface ProgressListener {
void onProgressShown();
void onProgressDismissed();
}
public void setProgressListener(ProgressListener progressListener) {
mProgressListener = progressListener;
}
public NewsfeedFragment() {
}
public static NewsfeedFragment newInstance() {
return new NewsfeedFragment();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OSUtil.setTranslucentStatusBar(getActivity().getWindow(), this.getContext());
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
mRootView = (SwipeRefreshLayout) inflater.inflate(R.layout.fragment_newsfeed, container, false);
mNewsfeedFramelayout = (FrameLayout) mRootView.findViewById(R.id.newsfeed_framelayout);
mProgressView = (ProgressBar) mRootView.findViewById(R.id.newsfeed_progress);
mPageIndictorView = (FrameLayout) mRootView.findViewById(R.id.page_indicator);
mPageIndicator = (TextView) mRootView.findViewById(R.id.page_indicator_text);
mRootView.setOnRefreshListener(this);
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) mRootView.findViewById(R.id.newsfeed_viewpager);
mPageChangeListener = this;
if (savedInstanceState != null) {
mTabPosition = savedInstanceState.getInt(EXTRA_TAB_POSITION);
mViewPager.setCurrentItem(mTabPosition);
}
fetchNewsFeed();
return mRootView;
}
private void fetchNewsFeed() {
if (NetworkUtils.isConnectedToInternet(getActivity())) {
if (NetworkUtils.getService() != null) {
if (mRootView.isRefreshing()) {
dismissProgress();
} else {
showProgress();
}
showProgress();
Call<Newsfeed> call = NetworkUtils.getService().getNewsfeed();
call.enqueue(new Callback<Newsfeed>() {
#Override
public void onResponse(Call<Newsfeed> call, Response<Newsfeed> response) {
if (response.isSuccessful()) {
dismissProgress();
mNewsfeed = response.body();
FragmentManager fragmentManager = getChildFragmentManager();
mNewsfeedPagerAdapter = new NewsfeedPagerAdapter(fragmentManager, mNewsfeed.getNewsfeedItems());
}
...
private void showProgress() {
// show the progress and notify the listener
if (mProgressListener != null){
setProgressIndicatorVisible(true);
notifyListener(mProgressListener);
}
}
public void dismissProgress() {
// hide the progress and notify the listener
if (mProgressListener != null){
setProgressIndicatorVisible(false);
mIsProgressShown = false;
notifyListener(mProgressListener);
}
}
public boolean isInProgress() {
return mIsProgressShown;
}
private void notifyListener(ProgressListener listener) {
if (listener == null){
return;
}
if (isInProgress()){
listener.onProgressShown();
}
else {
listener.onProgressDismissed();
}
}
Here is the IdlingResource:
public class ProgressIdlingResource implements IdlingResource, NewsfeedFragment.ProgressListener {
private ResourceCallback mResourceCallback;
private NewsfeedFragment mNewsfeedFragment;
public ProgressIdlingResource(NewsfeedFragment fragment) {
mNewsfeedFragment = fragment;
mNewsfeedFragment.setProgressListener(this);
}
#Override
public String getName() {
return "ProgressIdlingResource";
}
#Override
public boolean isIdleNow() {
return !mNewsfeedFragment.mIsProgressShown;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
mResourceCallback = callback;
}
#Override
public void onProgressShown() {
}
#Override
public void onProgressDismissed() {
if (mResourceCallback == null) {
return;
}
//Called when the resource goes from busy to idle.
mResourceCallback.onTransitionToIdle();
}
}
The fragment test:
public class NewsfeedFragmentTest {
#Before
public void setUp() throws Exception {
Espresso.registerIdlingResources(new ProgressIdlingResource((NewsfeedFragment) MainActivity.getCurrentFragment()));
}
#Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
#Test
public void getViewPager() throws Exception {
onView(allOf(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
withId(R.id.newsfeed_viewpager))).check(matches(isDisplayed()));
}
#Test
public void getNewsfeedItems() throws Exception {
onView(withId(R.id.page_indicator)).check(matches(isDisplayed()));
}
}
Retrofit is using OkHttp, and there is a standard way to setup IdlingResource for that matter. Refer to OkHttp IdlingResource

Activity presenter will update the tablayout fragments

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.

Espresso Wait for [RxIdlingResource] to become idle timed out

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;
}

Categories

Resources