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.
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.
Related
I have an activity with two fragment and want to be executed first fragment when its back from second fragment using back button. And i am using the add() when navigating first fragment to second fragment. Here is my scenario and code snippet:
First fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.one_fragment, container, false);
final Button button = (Button) view.findViewById(R.id.buttonChange);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
buttonClicked(v);
}
});
return view;
}
public void buttonClicked(View view) {
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment_Container, new TwoFragment());
fragmentTransaction.addToBackStack("sdfsf");
fragmentTransaction.commit();
}
Moving to Second fragment and here is the code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.two_fragment, container, false);
return view;
}
The problem is that, When I am navigating from first to second fragment and then back again in the first fragment using back button first fragment lifecycle method is not executing. Instead of using add() if I use replace() then lifecycle method are executing properly. I know its the difference between add() and replace() but I want to use add() and also want to have navigation callback to handle some logic when I back in the first fragment using back button.
Also tried below code:
fragmentManager.addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
Log.e(TAG, "onBackStackChanged: ");
// Update your UI here.
}
});
But its also calling multiple times and creating anomalies.
How can I handle this? Specially handle some logic in first fragment when I back from second fragment.
The easiest way I can think of is to set result when you're done with the second fragment that essentially tells the first fragment to "resume" via its onActivityResult method.
When you create an instance of Fragment B, call #setTargetFragment() and pass in Fragment A as your target fragment. Then when Fragment B is done and going to return to Fragment A, before it exits, you will set the result of it for Fragment A by calling:
getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_FRAGMENT_B_FINISHED,null)
///// horizontal scroll padding
Note that RESULT_FRAGMENT_B_FINISHED would be some static integer you define somewhere, like
public static final int RESULT_FRAGMENT_B_FINISHED = 123123;
Now in Fragment A all you need to do is override onActivityResult and check that the request code matches the request code integer from setTargetFragment and the result code also matches RESULT_FRAGMENT_B_FINISHED, if so you can run the code that would have been fired from onResume().
#getTargetFragment()
#onActivityResult()
#getTargetRequestCode()
Instead of passing data between the two fragments I recommend you to use a SharedViewModel.
The idea is that the first fragment observe some data for changes and the second one edit this data.
Example:
Shared ViewModel
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new
MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
First fragment
public class FirstFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model =
ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
Second fragment
public class SecondFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model =
ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.select(new Item("value 1","value 2");
}
}
You can read about ViewModels, LiveData and Architecture components starting from here: https://developer.android.com/topic/libraries/architecture/viewmodel#java
This answer is assuming that you want to execute some logic based on some data change. If it's not the case, you can explain what kind of logic do you want to execute and I will edit my answer.
I have two fragments, fragment_one (fragmentOne.class) and fragment_two (fragmentTwo.class). These fragments are being displayed inside ActivityMain.class.
When fragment_one is being displayed I want to set Image A as the background for ActivityMain.class.
When fragment_two is being displayed I want to set Image B as the background for ActivityMain.class
I can swap and set the background of ActivityMain.class when I am not using fragments (i use buttons as a test)....But, when I modify this for fragments I cannot get it to work.
Listed here you will see my ActivityMain.class
public class ActivityMain extends FragmentActivity {
//set variables for use of pageradapter and swipe screen
MyPageAdapter adapter;
ViewPager pager;
Context context = this;
LinearLayout swipeHomeScreen;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_activity_nav);
//Implement the adapater for the swipe screen on launch page and circle indicator
adapter = new MyPageAdapter(getSupportFragmentManager());
pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(adapter);
swipeHomeScreen = (LinearLayout) findViewById(R.id.swipeHomeScreen);
}
}
Here I have listed the code of one of my fragments where I am trying to share/pass the set the background image of ActivityMain.class...
public class ActivityNavSwipeTwo extends Fragment {
ActivityMain main;
public void onAttach(Activity activity) {
main = (ActivityMain) activity;
};
public static ActivityNavSwipeTwo newInstance() {
ActivityNavSwipeTwo fragment = new ActivityNavSwipeTwo();
return fragment;
}
public ActivityNavSwipeTwo() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_two, null);
main.swipeHomeScreen.setBackgroundResource(R.drawable.Image2);
return root;
}
}
My activity inflates the fragments fine, but the background image does not change.
How could I implement this for target api 23??
Thanks for your support.
Implement viewpager.onpagechangedlistener in your activity. Then add view.setonpageselecterlistener(this) in oncreated method. You can control selected fragment in implemented on pageselected method
Definitely you have any parent layout(Relative/Linear) for your activity. So you have to do following:
Create id of parent layout if not created.
access from fragment and change the background color of that parent layout.
Write this in Fragment's onCreateView() method.
relativeLayout = (RelativeLayout)getActivity().findViewById(R.id.relativelayout);
relativeLayout.setBackground(background);
I keep running into a recurring issue in many of my apps and have been using all kinds of work arounds to "solve" it, but this time I've had it and I want to figure out a real solution.
I am trying to build a tabbed layout with two tabs where each tab shows some data which should be obtained from the internet. Once the data is obtained it is cached on the device so it can be restored instantly the next time the app is opened (and will then be refreshed in the background).
To this effect I am trying to load the cached data and display it in a RecyclerView in the first tab, and I want to do this on activity create. Before I do this I obviously set up all the tab layout stuff so that the tabs should be properly loaded. The problem is that they are not, it seems the Fragments that make up the tab pages don't have their views yet, hence I cannot access the RecyclerView on them.
Here is my Activity code:
public class MainActivity extends NetworkBusActivity
{
// Views
ViewPager tabPager;
TabLayout tabLayout;
// Tab pager adapter
private ViewPagerAdapter adapter;
// Fragment one and two
private MenuFragment menuFragment;
private OrderFragment orderFragment;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Create the views
tabPager = (ViewPager) findViewById(R.id.tabPager);
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
// Setup the tab layouts
this.setupTabs();
// Show cached data
this.setCachedItems();
// Start loading new data in background
this.startLoading();
}
private void setupTabs()
{
// Create Fragments
menuFragment = new MenuFragment();
orderFragment = new OrderFragment();
// Setup adapter
adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(menuFragment, getString(R.string.title_menu));
adapter.addFragment(orderFragment, getString(R.string.title_orders));
tabPager.setAdapter(adapter);
// Setup the tab layout
tabLayout.setupWithViewPager(tabPager);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener()
{
// not shown
});
}
private void setCachedItems()
{
// Show cached data
ArrayList<Item> items = Cache.menu.getItems();
menuFragment.setItems(items);
}
private void startLoading()
{
// Start loading in background (not shown)
}
}
It should be straightforward: create the views, create the fragments, and setup the tab layout, then load the cached data.
The MenuFragment extends a base class ItemListFragment which defines the setItems method:
public class MenuFragment extends ItemListFragment
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.tab_menu, container, false);
return view;
}
#Override
protected ItemListAdapter getAdapter(ArrayList<Item> items, boolean categorize)
{
return new MenuListAdapter(this, R.layout.row_item, items, categorize);
}
}
public abstract class ItemListFragment extends Fragment
{
private RecyclerView recycler;
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
recycler = (RecyclerView) view;
recycler.setLayoutManager(linearLayoutManager);
recycler.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
}
public void setItems(ArrayList<Item> items)
{
recycler.setAdapter(getAdapter(items, true));
}
protected abstract ItemListAdapter getAdapter(ArrayList<Item> items, boolean categorize);
}
Again straightforward: create the view in onCreateView, then obtain the RecyclerView in onViewCreated, and finally set the items with an adapter.
The problem is simple: the method setCachedItems in MainActivity is called before the onCreateView or onViewCreated methods are called in the Fragments. Hence, the RecyclerView is null and I can't set its adapter. Even though I am creating a new instance of the Fragments and adding them to a functional TabLayout before I call that method.
There seems to be some delay before the views are created, but I need to set the items already when the activity is created.
Where am I going wrong, and how do I fix it?
In onViewCreated of fragment do this
((MainActivity)getActivity).setCachedData().....
Instead of on create of activity
I have two static fragments in same activity, in "fragmentA" i have a customized list, when an item is clicked must to appear a detail in "fragmentB", detail appear only when i change screen orientation, no automatically. I use this code in main activity for refresh but application restart(detail appear).
finish();
startActivity(getIntent());
Someone knows a better way to make appear detail automatically in "fragmentB" when i clicked some item from "fragmentA", always using two static fragments in same activity.
Don't use static references to hold a Fragment, it's a really bad practice.
Don't store the Context in a static reference. Or you could will leak memory.
Instead, implement an Interface:
//FragmentActivityTest
public class FragmentActivityTest extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentB fragmentB = new FragmentB();
FragmentA fragmentA = new FragmentA();
fragmentA.setFragmentBHandler(fragmentB);
//Perform transactions etc
}
}
//FragmentA
public class FragmentA extends Fragment {
private FragmentBHandler _handler;
public void setFragmentBHandler(FragmentBHandler handler) {
_handler = handler;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
((ListView) getView().findViewById(R.id.list_view)).setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
_handler.updateDetail();
}
});
}
}
//FragmentB
public class FragmentB extends Fragment implements FragmentBHandler {
#Override
public void updateDetail() {
//do your work
}
}
You should use an event bus like greenrobot or otto. FragmentB subscribe to an event, and FragmentA post that event. When you click on an item, you'll send an event, and the subscriber will execute your action (show details).
Without showing code, I can only guess your current implementation.
The proper way to communicate between fragments is
Pass data to the parent activity from Fragment A on item click,
Activity passes this data to fragment B by finding the fragment in fragment manager and call a method in fragment B,
That method in fragment B should determine if it should populate the detail.
I have TabActivityGroup:
MainActivity class contain some tab, that name loading from db. Sales, Admin, Inquiry like wise I have tab name
For Sales I created SalesActivityGroup.That class is :
public class SalesActivityGroup extends ActivityGroup {
public static SalesActivityGroup group;
private ArrayList<View> history;
private LocalActivityManager mActivityManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.history = new ArrayList<View>();
group = this;
mActivityManager = getLocalActivityManager();
Intent i = new Intent(getBaseContext(), SalesRouteActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("positions", -1);
i.putExtras(bundle);
View view = mActivityManager.startActivity("Sales",i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP )).getDecorView();
replaceView(view);
}
public void replaceView(View v) {
history.add(v);
setContentView(v);
}
public void back(){
if ( history.size() > 1 ){
history.remove(history.size() - 1);
View v = history.get(history.size() - 1);
setContentView(v);
}
else {
this.finish();
}
}
#Override
public void onBackPressed() {
SalesActivityGroup.group.back();
}
}
SalesRouteActivity is first Activity .In there i want to set up the title name.I did using this ways.But not working
public class SalesRouteActivity extends Activity{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.sales_routes);
//getWindow().setTitle("Route");
View viewToLoad = LayoutInflater.from(SalesActivityGroup.group).inflate(R.layout.sales_routes, null);
this.setContentView(viewToLoad);
//this.setTitle("Route");
//getWindow().setTitle("Route");
SalesActivityGroup.group.setTitle("Route");
}
}
Please advice me How can i set the Title name.
Thanks in advance
You can access the parent tab activity like
getParent().getParent().setTitle("New Tilte");
EXPLANATION:
Based on my understanding,
When you call the getParent the first time, you get the activity group that started the child activity.
When you call getParent the second time, you will get the tab activity that started the activity group.
setTitle should work for the activity window which is held by the tabactivity. The sub activities are rendered in the framelayout of the tab activity. So in the child activities access the parent tab activity to set the title.
The best way to do this is to implement the method
protected void onChildTitleChanged(Activity childActivity,CharSequence title) {
super.onChildTitleChanged(childActivity, title);
setTitle(title);
}
Implement this method in the parent activity. For example, In my case I have three activities.
Home Activity
Artists Activity
Album Activity
My Home activity contains a TabHost with Artists activity and Album activity.
I implemented the above method in the Home Activity. The title for Artists activity and Album activity is set in the OnResume methods of those respective activities.
It is not advisable to use ActivityGroup, it has been deprecated.
Refer to this link
Please use Fragment and FragmentManager using Compatibility Library