Activities and fragments with the same features - android

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.

Related

How can I make connection between retrofit and RxJava in different classes?

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

MVP with Moxy. Presenter's callback methods in Fragment not called (getViewState() is not null)

I have a difficulties with MVP realization using Moxy library. I read Moxy's github pages and checked examples, but any solution doesn't helps me.
In MyFragment the callbacks of MyFragmentView methods not called, but in MyFragmentPresenter getViewState() returns not null.
I mean that
getViewState().showProgressDialog();
getViewState().setAccounts(accountsResponse);
getViewState().hideProgressDialog();
are called in MyPresenter, but in MyFragment
#Override
public void showProgressDialog() {
// some code
}
#Override
public void hideProgressDialog() {
// some code
}
#Override
public void setAccounts(AccountsResponse accounts) {
// some code
}
doesn't called.
Please help, what's wrong?
My code below.
Gradle
compile 'com.arello-mobile:moxy:1.5.5'
compile 'com.arello-mobile:moxy-app-compat:1.5.5'
compile 'com.arello-mobile:moxy-android:1.5.5'
annotationProcessor 'com.arello-mobile:moxy-compiler:1.5.5'
MyFragmentView
#StateStrategyType(AddToEndSingleStrategy.class)
public interface MyFragmentView extends MvpView {
void showProgressDialog();
void hideProgressDialog();
void showTextTestMessage(String s);
void showCouldNotRetrieveAccounts();
}
MyFragmentPresenter
#PerFragment
#InjectViewState
public class MyFragmentPresenter extends MvpPresenter<MyFragmentView> {
private ApiService apiService;
#Inject
public MyFragmentPresenter(ApiService apiService) {
this.apiService = apiService;
}
public void getAccounts() {
getViewState().showProgressDialog();
getCompositeDisposable().add(apiService.getAccounts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(accountsResponse -> {
if (accountsResponse.err_code != 0) {
throw new LogicException(accountsResponse.message);
}
return accountsResponse;
})
.subscribe(
accountsResponse -> {
getViewState().setAccounts(accountsResponse);
getViewState().hideProgressDialog();
},
throwable -> {
getViewState().hideProgressDialog();
getViewState().showCouldNotRetrieveAccounts();
}
)
);
}
}
MyFragment
public class MyFragment extends MvpAppCompatFragment implements MyFragmentView {
#Inject
#InjectPresenter
public MyFragmentPresenter mPresenter;
#ProvidePresenter
public MyFragmentPresenter providePresenter() {
return mPresenter;
}
#Override
public void onActivityCreated (#Nullable Bundle savedInstanceState){
ComponentManager.getInstance().getActivityComponent(getActivity()).inject((MyActivity) getActivity());
super.onActivityCreated(savedInstanceState);
mPresenter.getAccounts();
}
#Override
public void showProgressDialog() {
// some code
}
#Override
public void hideProgressDialog() {
// some code
}
#Override
public void setAccounts(AccountsResponse accounts) {
// some code
}
#Override
public void showCouldNotRetrieveBankAccounts() {
// some code
}
}
**MyFragmentPagerAdapter **
public class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
#Override
public Fragment getItem (int position){
switch (position) {
case 0:
//
case 1:
//
case 2:
return new MyFragment();
}
return null;
}
#Override
public int getCount() {
return 3;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
I think something went wrong with connecting dagger with moxy.
Check the following issue on GitHub:
https://github.com/Arello-Mobile/Moxy/issues/100
Guys solve the same problem there

Google Architecture Lifecycle Component Not Calling Fragment Lifecycle Events

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.

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.

Categories

Resources