Search code examples
androidandroid-fragmentsandroid-viewpagerandroid-tablayoutstart-activity

Activity With TabLayout Save State After New Activity Start


I have an Activity that contains a TabLayout linked to a ViewPager. The ViewPager contains three fragments, and each fragment has a RecyclerView containing a list of articles. When you click on an a list item, it starts a new Activity with the full article text. After I start the new Activity, then press the back button to return to the first Activity, it starts with the first tab selected.

I would like to be able to start a new Activity from any tab, then when the back button is pressed, it goes back to the previous activity with the same tab selected. And preferably, maintain the state within the RecyclerView (i.e. how far it is scrolled down). How can I achieve this?

I have tried using onSaveInstanceState to save the viewPager.getCurrentItem(). It worked for when the device was rotated, but the saved instance state does not seem to be called after a new Activity is started.


The tabbed Activity:

public class ArticleActivity extends BaseActivity {

    @Bind(R.id.toolbar) Toolbar mToolbar;
    @Bind(R.id.content) ViewPager mViewPager;
    @Bind(R.id.tabs) TabLayout mTabLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_article);
        ButterKnife.bind(this);
        setupActionBar();

        FragmentPagerAdapter adapter = new BaseFragmentPagerAdapter(this, getSupportFragmentManager(),
                NewsFragment.newInstance();
                EventsFragment.newInstance();
                BulletinFragment.newInstance();
        );

        mViewPager.setAdapter(adapter);
        mTabLayout.setupWithViewPager(mViewPager);
    }

    private void setupActionBar() {
        setSupportActionBar(mToolbar);

        ActionBar actionBar = getSupportActionBar();
        if(actionBar != null) {
            actionBar.setDisplayShowTitleEnabled(true);
            actionBar.setDisplayHomeAsUpEnabled(true);
        }
    }
}

And one of the fragments contained in the ViewPager:

public class NewsFragment extends BaseFragment implements
        Observer<DataResponse<NewsArticle>> {

    @BindDimen(R.dimen.divider_padding_start) float mDividerPadding;

    @Bind(R.id.recycler) RecyclerView mRecyclerView;
    @Bind(R.id.progress) RecyclerView mProgressBar;
    @Bind(R.id.refresh_layout) SwipeRefreshLayout mRefreshLayout;

    @Inject RestService mRestService;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_recycler_refresh, container, false);
        ButterKnife.bind(this, view);

        mRefreshLayout.setColorSchemeColors(
                ThemeUtil.getColorAttribute(getContext(), R.attr.colorAccent));
        mRefreshLayout.setProgressBackgroundColorSchemeColor(
                ThemeUtil.getColorAttribute(getContext(), R.attr.colorPrimary));
        mRefreshLayout.setOnRefreshListener(this::getArticles);

        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        mRecyclerView.addItemDecoration(new DividerDecoration(getContext(), mDividerPadding));

        getArticles();

        return view;
    }


    @Override
    public CharSequence getTitle(Context context) {
        return context.getString(R.string.title_fragment_news);
    }

    @Override
    publiv void onCompleted() {
        mRefreshLayout.setRefreshing(false);
    }

    @Override
    public void onError(Throwable exception) {
        Timber.d(exception, "Failed to retrieve articles");
    }

    @Override
    public void onNext(DataResponse<NewsArticle> response) {
        mProgressBar.setVisibility(View.GONE);

        ArticleAdapter adapter = new ArticleAdapter(getContext(), response.data());
        adapter.setOnItemClickListener(this::onItemClick);
        mRecyclerView.swapAdapter(adapter, false);
    }

    private void getArticles() {
        mRestService.getNews().subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread()).subscribe(this);
    }

    public void onItemClick(Article article) {
        Intent intent = new Intent(getActivity(), ArticleDetailActivity.class);
        intent.putExtra("article", article);
        startActivity(intent);
    }
}

I was curious, so I just tested it. The Activity doesn't actually get destroyed when I start the new Activity. It only gets destroyed (then recreated) after I press the back button. I don't understand why it would do that, if the Activity does not need to be destroyed when a new one starts, why would it need to be destroyed when it simply comes to the foreground?

I have another activity (my SettingsActivity) that does not have a parent Activity, and just calls finish() when the back button is pressed. If I start this Activity from my ArticleActivity, the ArticleActivity never gets destroyed, and saves its state perfectly.


Solution

  • I found my answer here: ActionBar 'up' button destroys parent activity, 'back' does not

    And here: How can I return to a parent activity correctly?

    The parent (ArticleActivity) was getting destroyed after the Back button was pressed in the child Activity because that is the behavior of the "standard" launch mode. I set android:launchMode="singleTop" for the ArticleActivity in the manifest, which gives me the desired launch behavior. Now when Back is pressed in the child Activity, the parent is not recreated.