Android Studio 0.5.8
Hello,
I have one activity (MainActivity) that will host 2 fragments (ListAddressBookFragment, AddAddressBookFragment) (only one at a time). The initial fragment will be the ListAddressBookFragment and will be inflated when MainActivity onCreate gets called.
/* MainActivity.java */
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Add and display fragment */
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.flFragmentContainer);
/* Create new fragment if this hasn't already been done */
if(fragment == null) {
fragment = new ListAddressBookFragment();
fragmentManager.beginTransaction()
.add(R.id.flFragmentContainer, fragment)
.commit();
}
}
In the ListAddressBookFragment I have a option menu to add a new addressbook item. So this will call call MainActivity. So I want to replace ListAddressBookFragment with AddAddressBookFragment. However, because the code above is hardcoded I am wondering is there anyway to do this on the fly?
/* ListAddressBookFragment.java */
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.new_addressbook) {
Intent intent = new Intent(getActivity(), MainActivity.class);
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
Many thanks for any suggestions,
I would recommend you to read this article, Communicating with fragments
Basically, you need to call
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
Also, consider using interface within fragment that activity implements.
Why don't you pass data to the MainActivity to indicate the mode you want the activity to be in?
intent.putExtra("mode", "addressbook");
In the MainActivity, you do the below.
String mode = (String)getIntent().getStringExtra("mode");
if ("addressbook".equals(mode)) {
// Address book fragment
} else {
// ListAddressBookFragment
}
Good Luck
Related
I have a NavigationViewer activity which has 3 fragments. I want that every time the user selects an item from NavigationViewersliding menu items the app will transact a new fragment object of the selected kind.
for Example I have a NavigationViewer menu item Called "MyFragment"
So I am trying this Code on that item:
MyFragment myFragment = new MyFragment();
fragmentTransaction.replace(R.id.RR, myFragment , "nav_MyFragment ").commit();
But this causes a problem that if user selected "MyFragment" from menu while it is active [seen to user] it will create a new object.
and I want to create that new object only if transacting from some fragment to another.
Any Suggestions?
Edit: retrieving the fragment by tag and then checking if isVisble() or if isAdded() gives null exception
You can keep a Fragment variable and assign your active fragment there, and then check the getClassName() and if it's the clicked one is the same as the one you are currently displaying, you don't load a new one.
I'm going to use the code from this app I have in github.
Then in your Activity declare a Fragment variable like this:
Fragment active;
In your onOptionsItemSelected() method, you have to do the checking for the item pressed.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.nav_item1) {
if (active.getClass().getSimpleName().equals("FragmentA")) {
// then this fragment is already being shown, therefore do nothing
} else {
FragmentA fragtoadd = new FragmentA();
getFragmentManager().beginTransaction()
.remove(fragtoadd)
.commit();
active = fragtoadd; // the magic is here, everytime you add a new fragment, keep the reference to it to know what fragment is being shown
}
return true;
} else if (id == R.id.nav_item2) {
// do the same as above but for FragmentB, and so on
return true;
}
// ... check all your items
return super.onOptionsItemSelected(item);
}
If you have your code in Github so that I can have a look it would be better.
Hope it helps!
U have to add fragment instance into backstack
getSupportedFragmentManager.addToBackStack(fragment.gettag)
I assume you can detect the actual item clicks, so what I do is this:
private static final String TAG_FRAGMENT_SOME_NAME = "something";
mFragmentManager = getSupportFragmentManager(); // or getFragmentManager() if not using support library
mFragmentTransaction = mFragmentManager.beginTransaction();
Fragment myFragment = mFragmentManager.findFragmentByTag(TAG_FRAGMENT_SOME_NAME);
if (myFragment == null) {
myFragment = MyFragment.newInstance();
}
mFragmentTransaction.replace(R.id.RR, myFragment, TAG_FRAGMENT_SOME_NAME).commit();
I am using SLiding menu at My App and according to menu item , I am changing always the fragment at activity which have binded already to menu. But There is performance issue. it is freezing when i am attaching the fragments. but after the wiew created. the performance is normal. I am replacing the fragments after call the toggle function at menu.
is there anyone to have any opinion about it ?
just call it as:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Pass the event to ActionBarDrawerToggle, if it returns
// true, then it has handled the app icon touch event
if (item.getItemId() == R.id.evnetCalender) {
if (!isToggle) {
item.setIcon(R.drawable.ic_view_list_white_24dp);
setFragment(caldroidCalendarFragment);
isToggle = true;
} else {
item.setIcon(R.drawable.ic_event_white_24dp);
setFragment(scheduleEventFragment);
isToggle = false;
}
}
and set your fragment when you toggle it
private void setFragment(Fragment fragment1) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.content, fragment1);
fragmentTransaction.commit();
}
this content identifier is your frame layout id in which your fragment take place
I'm new in Android..
I have a question about MasterDetail template.
I have an Activity to manage user login. When the login data are correct I need to show the Master Detail template.
So, in Android Studio, i added a Master/Detail Flow activity in my project.
In my validateLogin() method, after the login data are correct, I have to show Activitys in the Master/Detail flow and I use this code:
Intent myIntent = new Intent(Login.this, MasterDetailListActivity.class);
startActivity(myIntent);
finish();
I'm not secure if this code is correct and I would to show also the actionBar (actually I can see the ActionBar in detailView only).
****** EDIT ********
This is the code of my list activity
public class BusinessPartnerListActivity extends FragmentActivity
implements BusinessPartnerListFragment.Callbacks {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_businesspartner_list);
if (findViewById(R.id.businesspartner_detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
// In two-pane mode, list items should be given the
// 'activated' state when touched.
((BusinessPartnerListFragment) getSupportFragmentManager()
.findFragmentById(R.id.businesspartner_list))
.setActivateOnItemClick(true);
}
// TODO: If exposing deep links into your app, handle intents here.
}
/**
* Callback method from {#link BusinessPartnerListFragment.Callbacks}
* indicating that the item with the given ID was selected.
*/
#Override
public void onItemSelected(String id) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(BusinessPartnerDetailFragment.ARG_ITEM_ID, id);
BusinessPartnerDetailFragment fragment = new BusinessPartnerDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.businesspartner_detail_container, fragment)
.commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, BusinessPartnerDetailActivity.class);
detailIntent.putExtra(BusinessPartnerDetailFragment.ARG_ITEM_ID, id);
startActivity(detailIntent);
}
}
}
...and detail activity...
public class BusinessPartnerDetailActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_businesspartner_detail);
// Show the Up button in the action bar.
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
// (e.g. when rotating the screen from portrait to landscape).
// In this case, the fragment will automatically be re-added
// to its container so we don't need to manually add it.
// For more information, see the Fragments API guide at:
//
// http://developer.android.com/guide/components/fragments.html
//
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(BusinessPartnerDetailFragment.ARG_ITEM_ID,
getIntent().getStringExtra(BusinessPartnerDetailFragment.ARG_ITEM_ID));
BusinessPartnerDetailFragment fragment = new BusinessPartnerDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.add(R.id.businesspartner_detail_container, fragment)
.commit();
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpTo(this, new Intent(this, BusinessPartnerListActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}
***** EDIT (Video) ******
Video of issue on ActionBar animation
https://www.dropbox.com/s/af07ovbv36pn44x/actionbab_issue.mov?dl=0
You need to extend from ActionBarActivity instead of FragmentActivity. Change your listview activity definition to:
public class BusinessPartnerListActivity extends ActionBarActivity
implements BusinessPartnerListFragment.Callbacks {
// rest of the code remains the same ...
// ...
// ...
}
Try this. This will work.
Yes your are doing it right, i mean your code is perfect. If you want to finish your current then
finish();
is perfect other wise no need to call it.
I have trouble why learning Android fragments.
In my app I have one activity with 3 fragments. Each fragment is to replace previous after some user action. My main xml layout is simple FrameLayout with ID 'container'. What I do in MainActivity onCreate() method is adding default fragment to container:
public class HolderActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
....
FragmentList listFragment = new FragmentList();
// Add the fragment to the 'container'
getSupportFragmentManager().beginTransaction()
.add(R.id.container, listFragment).commit();
Initial screen works just fine.
Then I add other fragment, based on button click:
// Go to settings fragment
FragmentSettings mFragmentSettings = new FragmentSettings();
getFragmentManager().beginTransaction().replace(R.id.container, mFragmentSettings).commit();
The problem is that when I click needed button App doesn't replace fragments, it just adds new fragment over the old one, so they collapse. Could You please tell me what I am doing wrong here?
EDIT:
When I try to use getSupportFragmentManager() in my onOptionsItemSelected method like this:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
// Go to settings fragment
FragmentSettings mFragmentSettings = new FragmentSettings();
getFragmentManager().beginTransaction()
.replace(R.id.container, mFragmentSettings).commit();
}
return super.onOptionsItemSelected(item);
}
I get this error:
"The method replace(int, Fragment) in the type FragmentTransaction is not applicable for the arguments (int, FragmentSettings)"
Why are you using a supportFragmentManager and then the normal FragmentManager?
getSupportFragmentManager() and getFragmentManager().
If you're using ActionBarActivity, then use support one. If Activity then the normal one.
I have an Activity where I load in a ListFragment and, upon clicking, it drills down a level and a new type of ListFragment is shown, replacing the original one (using the showFragment method below). This is placed on the back stack.
At the beginning, the activity shows the default title in the action bar (i.e. it's set automatically based on the application's android:label).
When showing the list for the next level in the hierarchy, the name of the item clicked on should become the action bar's title.
However, when pressing Back, I would like the original default title to be restored. This isn't something FragmentTransaction knows about, so the title isn't restored.
I've vaguely read about FragmentBreadCrumbs, but this seems to require using a custom view. I'm using ActionBarSherlock and would prefer to not have my own custom title view.
What is the best way of doing this? Is it possible without a load of boilerplate code and having to keep track of the titles shown along the way?
protected void showFragment(Fragment f) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, f);
ft.addToBackStack(null);
ft.commit();
}
In every fragment and every activity I change the title like this. This way the active title will always be correct:
#Override
public void onResume() {
super.onResume();
// Set title
getActivity().getActionBar()
.setTitle(R.string.thetitle);
}
There is some cases where onResume isn't called inside fragments. In some of these cases we can use:
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser) {
// Set title
getActivity().getActionBar()
.setTitle(R.string.thetitle);
}
}
As the original answer is quite old, this might come of help as well. As the documentation states, one might want to register a listener to listen on the back stack changes in the hosting Activity:
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Update your UI here.
}
});
Then, identify the situation in the callback method and set a proper title, without accessing the ActionBar from the Fragment.
This is a more elegant solution as the Fragment doesn't have to know about the ActionBar existence and Activity is usually the place that is managing the backstack so having it handled over there seems to be more appropriate. Fragment should at all time be considered only by its own content, not the surroundings.
More on the topic in the documentation.
Let the controlling activity do all the work as follows:
Listen for backstack events (in onCreate() of activity):
// Change the title back when the fragment is changed
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
Fragment fragment = getFragment();
setTitleFromFragment(fragment);
}
});
Get the current fragment from the container:
/**
* Returns the currently displayed fragment.
* #return
* Fragment or null.
*/
private Fragment getFragment() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
return fragment;
}
Set the fragment inside the content view:
private void setFragment(Fragment fragment, boolean addToBackStack) {
// Set the activity title
setTitleFromFragment(fragment);
.
.
.
}
Warpzit is right. This also solves title problem when orientation of device is changed. Also if you use support v7 for action bar, you can get action bar from fragment like this :
#Override
public void onResume() {
super.onResume();
((ActionBarActivity)getActivity()).getSupportActionBar().setTitle("Home");
}
It is best to let the OS do as much of the work as possible.
Assuming each fragment is properly named using .addToBackStack("title") then
you can override onBackPressed something like this to achieve desired behavior:
// this example uses the AppCompat support library
// and works for dynamic fragment titles
#Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
if (count <= 1) {
finish();
}
else {
String title = fragmentManager.getBackStackEntryAt(count-2).getName();
if (count == 2) {
// here I am using a NavigationDrawer and open it when transitioning to the initial fragment
// a second back-press will result in finish() being called above.
mDrawerLayout.openDrawer(mNavigationDrawerFragment.getView());
}
super.onBackPressed();
Log.v(TAG, "onBackPressed - title="+title);
getSupportActionBar().setTitle(title);
}
}
I use a similar solution to Lee approach, but replacing onBackStackChanged() method instead.
First I set the fragment name when adding the transaction to the back stack.
getSupportFragmentManager().beginTransaction()
.replace(R.id.frame_content, fragment)
.addToBackStack(fragmentTitle)
.commit();
Then I override the onBackStackChanged() method and I call setTitle() with the last backstack entry name.
#Override
public void onBackStackChanged() {
int lastBackStackEntryCount = getSupportFragmentManager().getBackStackEntryCount() - 1;
FragmentManager.BackStackEntry lastBackStackEntry =
getSupportFragmentManager().getBackStackEntryAt(lastBackStackEntryCount);
setTitle(lastBackStackEntry.getName());
}
Use Fragments method:
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
It is called on every Fragment appearance, but onResume is not.
The best approach is to make use of the android provided Interface OnBackStackChangedListener method onBackStackChanged().
Lets say we have a navigation drawer with 4 options to which the user can navigate to. In that case we will have 4 fragments. Lets see the code first and then I will explain the working.
private int mPreviousBackStackCount = 0;
private String[] title_name = {"Frag1","Frag2","Frag3","Frag4"};
Stack<String> mFragPositionTitleDisplayed;
public class MainActivity extends ActionBarActivity implements FragmentManager.OnBackStackChangedListener
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
....
....
....
getSupportFragmentManager().addOnBackStackChangedListener(this);
mFragPositionTitleDisplayed = new Stack<>();
}
public void displayFragment() {
Fragment fragment = null;
String title = getResources().getString(R.string.app_name);
switch (position) {
case 0:
fragment = new Fragment1();
title = title_name[position];
break;
case 1:
fragment = new Fragment2();
title = title_name[position];
break;
case 2:
fragment = new Fragment3();
title = title_name[position];
break;
case 3:
fragment = new Fragment4();
title = title_name[position];
break;
default:
break;
}
if (fragment != null) {
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container_body, fragment)
.addToBackStack(null)
.commit();
getSupportActionBar().setTitle(title);
}
}
#Override
public void onBackStackChanged() {
int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
if(mPreviousBackStackCount >= backStackEntryCount) {
mFragPositionTitleDisplayed.pop();
if (backStackEntryCount == 0)
getSupportActionBar().setTitle(R.string.app_name);
else if (backStackEntryCount > 0) {
getSupportActionBar().setTitle(mFragPositionTitleDisplayed.peek());
}
mPreviousBackStackCount--;
}
else{
mFragPositionTitleDisplayed.push(title_name[position]);
mPreviousBackStackCount++;
}
}
In the code shown we have the displayFragment() method. Here I display the fragment on the basis of option chosen from the navigation drawer.The variable position corresponds to the position of the item clicked from the ListView or RecyclerView in the navigation drawer. I set the actionbar title accordingly with getSupportActionBar.setTitle(title), where the title stores the appropriate title name.
Whenever we click the item from nav drawer a fragment is displayed depending on the item clicked to the user. But on the back end side this fragment is added to the backstack and the method onBackStachChanged(), gets hit. What I have done is that I have created a variable mPreviousBackStackCount and initialized it to 0. I have also created an additional stack which will store the action bar title names. Whenever I add a new fragment to the backstack, I add the corresponding title name to my created stack. On the opposite side whenever I press the back button onBackStackChanged() is called and I pop the last title name from my stack and set the title to the name derived by the peek() method of the stack.
Example:
Lets say our android backstack is empty:
Press Choice 1 from nav drawer:
onBackStachChanged() is called and the Fragment 1 is added to android backstack, backStackEntryCount is set to 1 and Frag1 is pushed to my stack and size of mFragPositionTitleDisplayed becomes 1.
Press Choice 2 from nav drawer:
onBackStachChanged() is called and the Fragment 2 is added to android backstack, backStackEntryCount is set to 2 and Frag2 is pushed to my stack and size of mFragPositionTitleDisplayed becomes 2.
Now we have 2 elements both in the android stack and my stack. When you press back button onBackStackChanged() is called and the value of backStackEntryCount is 1. The code enters the if part and pops out the last entry from my stack. So, the android backstack has only 1 fragment - "Fragment 1" and my stack has only 1 title - "Frag1". Now I just peek() the title from my stack and set the action bar to that title.
Remember: To set the action bat title use peek() and not pop() else your application will crash when you open more than 2 fragments and try to go back by pressing back button.
You can Solve with onKeyDown!
I have a bool
mainisopen=true <-- MainFragment is Visible
other Fragment mainisopen=false
and here is My Code:
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == false) {
mainisopen = true;
HomeFrag fragment = new HomeFrag();
FragmentTransaction fragmentTransaction =
getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragmet_cont, fragment);
fragmentTransaction.commit();
navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.getMenu().findItem(R.id.nav_home).setChecked(true);
navigationView.setNavigationItemSelectedListener(this);
this.setTitle("Digi - Home"); //Here set the Title back
return true;
} else {
if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == true) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Wollen sie die App schliessen!");
builder.setCancelable(true);
builder.setPositiveButton("Ja!", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
System.exit(1);
}
});
builder.setNegativeButton("Nein!", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getApplicationContext(), "Applikation wird fortgesetzt", Toast.LENGTH_SHORT).show();
}
});
AlertDialog dialog = builder.create();
dialog.show();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
As described here my solution is adding this code to MainActivity onCreate method(): and changing actionbar title
FragmentManager fragmentManager=getSupportFragmentManager();
fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
Fragment currentFragment = fragmentManager.findFragmentById(R.id.My_Container_1_ID);
currentFragment.onResume();
}
});
and changing actionbar title in fragment's onResume() method
#Override
public void onResume() {
super.onResume();
AppCompatActivity activity = (AppCompatActivity) getActivity();
ActionBar actionBar = activity.getSupportActionBar();
if(actionBar!=null) {
actionBar.setTitle("Fragment Title");
actionBar.setSubtitle("Subtitle");
}
}
To update the actionbar title on back press. Just simply put
getActivity.setTitle("title")
inside onCreateView method.