I need your help regarding my application flow.
MainActivity (with Navigation Drawer)
-- Fragment A
-- Fragment B
-- Fragment C (articles list view)
ArticleActivity
-- Fragment D (article detail view)
Fragment C (MainActivity) displays a list of items (ListView). Selecting an item leads to fragment D (handle by ArticleActivity) which presents that item in more detail.
Fragment D displays a "Up" button that should allow the user to returns to previous screen (the detail view). The problem is that when the "Up" button is pressed the previous activity is displayed but not the previous activated fragment (the defaut fragment (A) is instead displayed).
My current code:
public class FragmentC extends Fragment {
public static FragmentC newInstance() {
FragmentC fragment = new FragmentC();
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_courses, container, false);
...
// Somewhere in my code I have a onClickListener to launch the detail view activity
setOnClickListener(new OnCardClickListener() {
#Override
public void onClick(Card card, View view) {
Intent i = new Intent(getActivity(), ArticleActivity.class);
i.putExtra("articleIndex", mArticle.getId());
startActivity(i);
}
});
...
return view;
}
}
public class ArticleActivity extends Activity {
private long mArticleIndex;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_course);
mArticleIndex = getIntent().getExtras().getLong("articleIndex", 0);
// Set up action bar:
// Specify that the Home button should show an "Up" caret, indicating that touching the
// button will take the user one step up in the application's hierarchy.
final ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
// Place an ArticleFragment as our content pane
ArticleFragment fragment = ArticleFragment.newInstance(mArticleIndex);
getFragmentManager().beginTransaction().replace(R.id.container, fragment).commit();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// This is called when the Home (Up) button is pressed in the action bar.
Intent upIntent = new Intent(this, MainActivity.class);
upIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(upIntent);
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
I think that I can resolve this issue by passing parameters to my Intent. For example i.putExtra("displayFragment", FragmentD) and then on the MainActivity retrieve it and tell to the FragmentManager to display the wanted fragment. I do not know if it's can work.
But it's maybe not the right way to achieve that?
Do you have any better workaround?
Related
So far I have had a MainActivity that swaps through Fragments. But I have implemented a FragmentActivity in order to use a ViewPager and swap through Fragments in one of my Drawer's section.
But when I click on my Drawer's item that creates the ViewPager, the Drawer disappears all together.
Here is how I start my FragmentActivity:
private Fragment checkFragment(int itemId) { // Checks what item the user has pressed and calls the right Fragment / Activity
Fragment fragment = null;
switch (itemId) {
// HOME (If user clicks on HOME in Drawer)
case R.id.nav_home:
fragment = new Home(); // Set Fragment to be used
break;
// STATS GENERAL (If user clicks on General Stats)
case R.id.nav_stat_general:
startActivity(new Intent(this, GeneralStatistics.class)); // Start the Fragment Activity
fragment = null;
break;
}
return fragment;
}
And here is my Fragment Activity onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.statistics_general);
mStatisticsPagerAdapter = new StatisticsPagerAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mStatisticsPagerAdapter);
}
How may I keep my Drawer even though I have gone from one Activity to another?
To keep your Navigation Drawer. you must have to replace the viewpager content by another Fragment.
From your code
startActivity(new Intent(this, GeneralStatistics.class));
This line clearly shows that you are Intenting to another activity (not a Fragment which replaces your viewpager content)
I recommend you to take a look at this Example.
Solution / work around to my problem:
I do not use an ActivityFragment anymore but simply a Fragment and replaced its onCreate with the following:
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.statistics_general_vp, container, false);
// No more setContentView
mViewPager = (ViewPager) view.findViewById(R.id.pager);
mStatisticsPagerAdapter = new StatisticsPagerAdapter(getActivity().getSupportFragmentManager());
mViewPager.setAdapter(mStatisticsPagerAdapter);
return view;
}
I have an implementation of fragment as explained here. I have expanded the BaseFragment class to handle navigation icon onClickListener. The code for that looks like this,
private void onBackButtonPressed(int tag)
{
switch (tag)
{
case Global.DISPLAY_STORE:
{
setTitle("Loyalty Cards",Color.BLACK);
setToolBar(getResources().getColor(R.color.white), Global.ADD_CARD,R.drawable.add_round_btn);
break;
}
case Global.ADD_CARD:
{
setTitle("Wallet", Color.WHITE);
setToolBar(getResources().getColor(R.color.colorPrimary), Global.DISPLAY_STORE,R.drawable.icon_shopping);
getFragmentManager().popBackStack();
break;
}
case Global.CARD_DETAILS:
{
setTitle("Loyalty Cards",Color.BLACK);
setToolBar(getResources().getColor(R.color.white), Global.ADD_CARD,R.drawable.add_round_btn);
getFragmentManager().popBackStack();
break;
}
}
}
I am using this block of code to change the ToolBar icons and colors when back button is pressed.This code resides in BaseFragment.
Here is how I implement the code to handle back press,
I have my fragment extending the BaseFragment
public class CardDetailsFragment extends BaseFragment
Now inside the BaseFragment onCreate I have this code,
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mainActivity = (MainActivity) getActivity();
ImageButton righBarButton = (ImageButton) mainActivity.getToolbar().findViewById(R.id.btn_ToolBarRightBtn);
righBarButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onRighBarButtonClicked(Integer.parseInt(v.getTag().toString()));
}
});
mainActivity.getToolbar().setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onBackButtonPressed(BACK_BUTTON_TAG);
}
});
getFragmentManager().addOnBackStackChangedListener(this);
shouldDisplayHomeUp();
}
By this every fragments back button press and the right bar button item click in toolbar are handled.
Consider the scenario I have three fragments A , B and C.
From Fragment A I open Fragment B and Then Fragment C. Now From fragment C I click the back button to reach fragment B. The back button event is handled by above code and it works fine.
Now from Fragment B, I click the back button to reach Fragment A. But when I click back button I am getting exception
java.lang.IllegalStateException: Fragment CardDetailsFragment{d8b35b5} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:636)
here the CardDetailsFragment is the FragmentC, but I am clicking the
back button from Fragment B
The first time the user presses the back button, Fragment C is no longer necessary and thus it is detached from the activity. The problem lies in the fact that even though Fragment C is detached, it is still registered as a listener for back stack change events.
The second time the user presses the back button, your onBackStackPressed() method is called in Fragment C. When getResources() is executed, there is no activity attachment so getResources() fails.
Something like this in your fragment should fix it:
#Override
public void onDestroy() {
getFragmentManager().removeOnBackStackChangedListener(this);
super.onDestroy();
}
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.
I want to Put One back Button on Action Bar And After clicking on that I want to go back to an Activity. I am Writing Following code , but i am not able to achieve the same. Please Help me. Thnks
public class NutrientDailyTrackerActivity extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.activity_nutrient_details, container, false);
ActionBar actionBar = getActivity().getActionBar();
actionBar.setBackgroundDrawable(new ColorDrawable(Color.parseColor("#4CC1D2")));
actionBar.setTitle("Nutrient Tracker");
actionBar.setDisplayHomeAsUpEnabled(true);
// doing some task here
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// app icon in action bar clicked; go home
Intent intent = new Intent(getActivity(), CalorieMainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
Fragment is hosted inside an Activity. And ActionBar is too hosted in an Activity. So, you should write this thing inside the Activity where you are hosting the ActionBar.
Put the code of Actionbar inside onCreate of Activity where fragment is hosted. And don't forget to move the code of back button click handler to Activity too.
I followed the document example (down in the page) created a list in a main Activity , when list item is selected I start an DetailActivity which adds a DetailFragment to container .
(I simplified the example code, I didn't implement the landscape mode thing, just simply start DetailActivity when a list row is selected.)
In MainActivity, when list item is clicked I do:
#Override
public void onItemSelected(int index) {
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("index", index);
startActivity(intent);
}
My DetailActivity.java :
public static class DetailActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
// During initial setup, plug in the details fragment.
DetailsFragment details = new DetailsFragment();
details.setArguments(getIntent().getExtras());
//There is no layout xml for DetailActivity, we add the fragment programmatically to the activity.
FragmentManager fragManager = getSupportFragmentManager();
android.support.v4.app.FragmentTransaction fragTransaction = fragManager.beginTransaction();
fragTransaction.add(android.R.id.content, details);
fragTransaction.addToBackStack(null);
fragTransaction.commit();
}
}
The DetailFragment.java :
public static class DetailsFragment extends Fragment {
public static DetailsFragment newInstance(int index) {
DetailsFragment f = new DetailsFragment();
// Supply index input as an argument.
Bundle args = new Bundle();
args.putInt("index", index);
f.setArguments(args);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_detail, container, false);
}
}
}
I run my app, when I selecte a row, the DetailActivity is launched with the DetailFragment being shown on screen.
But I need to press physical Back button twice in order to go back the the list of MainActivity. Why I need to press twice back button?
I am testing on Android 4.4.4 device.
Because you're adding the fragment at runtime. The first press of the back button undoes the add of the fragment. The second finishes the activity. To prevent this, don't add the transaction where you first load the fragment to the back stack.