I have an Android app with a SlideMenu.
This is most of the MainActivity where I have the sideMenu:
public class MainActivity extends ActionBarActivity
{
private ActionBarDrawerToggle sideMenuToggle;
private DrawerLayout sideMenuLayout;
private SideMenuAdapter mAdapter;
...
private class DrawerItemClickListener implements ListView.OnItemClickListener
{
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Bundle data = new Bundle();
data.putString("title", options.get(posit).getString("visual_name"));
if(posit == Constants.INDEX_USERNAME)
goToView(ViewManager.EDITPROFILE, data);
else if((posit >= Constants.INDEX_SECTION_1) && (posit <= Constants.INDEX_SECTION_5))
{
Bundle category = options.get(posit);
data.putString("title", category.getString("visual_name"));
data.putString("visual_name", category.getString("visual_name"));
data.putString("name", category.getString("name"));
data.putInt("photo", category.getInt("photo"));
data.putInt("ico", category.getInt("cat_ico"));
data.putBoolean("is_root", true);
goToView(ViewManager.CATEGORYFRAGMENT, data);
}
}
}
public Fragment goToView(ViewInfo _viewInfo, Bundle bundle)
{
try
{
if((this != null) && !this.isFinishing())
{
ViewManager myVMgr = viewMgr;
return myVMgr.show(_viewInfo, bundle, false);
}
} catch (IllegalStateException e) {
}
return null;
}
}
And this is most of the code of the ViewManager where I switch between sections (they are fragments):
public class ViewManager
{
private Activity context = null;
...
public Fragment show(ViewInfo _newFragInfo, Bundle _data, boolean back)
{
Fragment currentFragment = null;
Fragment newFragment = null;
final FragmentManager fm = context.getFragmentManager();
ViewInfo _lastFragInfo = lastViewData != null ? lastViewData.getViewInfo() : null;
// In this app we must support changing between same fragment class
//if((_lastFragInfo != null) && _newFragInfo.getIdView().equalsIgnoreCase(_lastFragInfo.getIdView())) {
// return null;
//}
FragmentTransaction ft = fm.beginTransaction();
if(_newFragInfo.getIsRoot())
{
Iterator<ViewData> iter = viewStack.iterator();
ViewData viewData;
while (iter.hasNext())
{
viewData = iter.next();
if(!viewData.getViewInfo().getIsRoot())
{
currentFragment = fm.findFragmentByTag(viewData.getViewInfo().getIdView());
if (currentFragment != null)
{
ft.remove(currentFragment);
}
}
iter.remove();
}
}
// Hide current fragment
if (_lastFragInfo != null)
{
currentFragment = fm.findFragmentByTag(_lastFragInfo.getIdView());
if (currentFragment != null)
{
if(!back)
ft.detach(currentFragment);
else
ft.remove(currentFragment);
}
}
// Show new fragment
if (_newFragInfo != null)
{
if(_newFragInfo.getIsRoot() || back) // only tabs are reusable fragment
newFragment = fm.findFragmentByTag(_newFragInfo.getIdView());
if (newFragment == null)
{
newFragment = Fragment.instantiate(context, _newFragInfo.getClaseView().getName());
ft.add(R.id.frame_content, newFragment, _newFragInfo.getIdView());
}
else
{
ft.attach(newFragment);
}
if(_data == null)
_data = new Bundle(1);
if(!_data.containsKey("title"))
_data.putString("title", context.getResources().getString(_newFragInfo.getTitle()));
if(!_newFragInfo.getIsRoot() && !back)
{
viewStack.add(lastViewData);
}
((BaseFragment)newFragment).setData(_data);
}
lastViewData = new ViewData(_newFragInfo, _data);
ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
ft.commitAllowingStateLoss();
fm.executePendingTransactions();
return newFragment;
}
}
In the sideMenu, "Edit profile" option has its own Fragment and Section 1 to Section 5 use the same fragment but I load a different content.
This is working well so far and when the user opens the sidemenu and changes between sections they are loaded correctly.
But now I've uploaded my code to use target sdk 26 and it doesn't compile because ActionBarActivity is no longer available so I've made these changes:
public class MainActivity extends ActionBarActivity
to this
public class MainActivity extends AppCompatActivity
and inside ViewManager
private Activity context = null;
to this
private AppCompatActivity context = null;
This now compiles but when I change between sections the data of the new section is no loaded and I keep watching the content of the previous section.
Some examples:
1)
1.1) I select Section1 (I see the content of Section1)
1.2) I select Edit Profile (I see the content of Edit profile)
1.3) I select Section3 (I see the content of Section3)
2)
2.1) I select Section1 (I see the content of Section1)
2.2) I select Section3 (I don't see the content of Section3 but the content of Section1).
Checking this I've seen that the problem is because with AppCompatActivity, onCreate, onCreateView and onResume methods of my CategoryFragment are called in steps 1.1, 1.3 and 2.1 but not in step 2.2.
Am I missing something? Should I make a new change so the Fragments transactions are made correctly with AppCompatActivity?
-- EDIT --
The setData method of the Fragment is this:
public void setData(Bundle _newdata)
{
data = _newdata;
}
I've thought to change it to this:
public void setData(Bundle _newdata)
{
data = _newdata;
// Some code to force the refresh/reloading of the Fragment
}
But I'm not sure if this is a tricky way to fix this, and by the way, I don't know how to force a fragment to reload itself.
Related
I am invoking a fragment(GetStartFragment) from an activity(DiscoverActivity) and from the same fragment (GetStartFragment), I am replacing it with another fragment (SelectFragment). Now I need to remove the old fragment (GetStartFragment) when I perform onClick() method as below. I was trying to remove, but the fragment ID returns null. How to remove the old fragment from the fragment itself.
DiscoverActivity.java :
public class DiscoverActivity implements AppCompatActivity {
.....
case R.id.start:
showGetStartFragment();
return true;
.....
private void showGetStartFragment() {
lGetStartFragment = GetStartFragment.newInstance();
lGetStartFragment.show(getSupportFragmentManager(), lGetStartFragment.getTag());
}
}
**FragmentDiscover.java :**
public class GetStartFragment extends BaseBottomSheetDialogFragment {
public static GetStartFragment newInstance() {
return new GetStartFragment();
}
.....
#OnClick(R2.id.getstart_button)
void onGetStartButtonClick() {
boolean isStart = true;
/* Show Fragment 2 */
SelectFragment lSelectFragment = SelectFragment.newInstance();
SelectFragment.show(requireActivity().getSupportFragmentManager(), SelectFragment.getTag());
/*Remove previous fragment - Fragment 1*/
FragmentManager fm = requireActivity().getSupportFragmentManager();
Fragment fragment_ID = fm.findFragmentById(com.misc.exam.R.id.design_getstart);
FragmentTransaction fmt = fm.beginTransaction();
if (fragment_ID != null) {
fmt.remove(fragment_ID).commitAllowingStateLoss();
}
}
#Override
public void onDestroy() {
super.onDestroy();
FragmentManager fm = requireActivity().getSupportFragmentManager();
Fragment fragID = fm.findFragmentById(com.misc.exam.R.id.design_getting_started);
FragmentTransaction fmt = fm.beginTransaction();
if (fragID != null) {
fmt.remove(fragID).commitAllowingStateLoss();
}
}
...
}
Have you tried saving the current fragment in a separate field and replacing it?
Fragment currentFragment;
currentFragment = new NewFragment()
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,currentFragment)
I have an application where the MainActiviy works with a bottom navigation which switches between full screen fragments.
I've learnt to control the back button navigation by ensuring I add each fragment to the backstack when created.
fragmentManager.beginTransaction().add(R.id.contentContainer, fragment, fragment_tag).addToBackStack(fragment_tag).commit();
There is one type of fragment, the loading screen fragment, that I do not want added to the backstack so I exclude the addToBackStack() method when creating the fragment.
As shown in the gif below Somehow the loading fragment still appears when pressing the back button even though it is not on the backstack (I've confirmed this with the debugger).
If anyone could give me a hand in figuring out why it is showing up I'd be really grateful, it has plagued me for about a week and I'm out of ideas!
Here is the code:
package *package name*;
import *all import statements*
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<ArrayList> {
BottomNavigation mBottomBar;
private FloatingActionButton fab;
private FirebaseDatabase database;
private DatabaseReference DB_Storage_Ref, DB_Master_Ref;
FragmentManager fragmentManager;
CustomBottomBarSelectionListener bbListener;
CustomBackStackChangeListener cBSCL;
ArrayList<IngredientCard> master = new ArrayList<>();
ArrayList<IngredientCard> all = new ArrayList<>();
ArrayList<IngredientCard> fridge = new ArrayList<>();
ArrayList<IngredientCard> freezer = new ArrayList<>();
ArrayList<IngredientCard> pantry = new ArrayList<>();
ArrayList<IngredientCard> ingredient_imports = new ArrayList<>();
int arraysLoaded = 0;
boolean loadingComplete = false;
ArrayList<String> storageLocationList = new ArrayList<>();
Map<String, ArrayList<IngredientCard>> storageLocationMapLists = new HashMap<>();
final String[] tag = {null};
boolean backButtonPressed = false;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Establish FirebaseDatabase Instance and required DB References
database = FirebaseDatabase.getInstance();
DB_Storage_Ref = database.getReference("Storage");
DB_Master_Ref = database.getReference("Master");
// These Storage location must match branch titles in Firebase JSON database
// Create a list of all Storage Room Titles (matching realtime database branch names)
storageLocationList.add("All");
storageLocationList.add("Fridge");
storageLocationList.add("Freezer");
storageLocationList.add("Pantry");
// Create a hashmap mapping all storage room arrays to the associated storage room titles.
storageLocationMapLists.put("All", all);
storageLocationMapLists.put("Fridge", fridge);
storageLocationMapLists.put("Freezer", freezer);
storageLocationMapLists.put("Pantry", pantry);
// Associate UI to Variables
Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
fab = (FloatingActionButton) findViewById(R.id.fab);
mBottomBar = (BottomNavigation) findViewById(R.id.BottomNavigation);
fragmentManager = getSupportFragmentManager();
bbListener = new CustomBottomBarSelectionListener(this);
mBottomBar.setOnMenuItemClickListener(bbListener);
cBSCL = new CustomBackStackChangeListener(this);
fragmentManager.addOnBackStackChangedListener(cBSCL);
// Load arrays with data from Firebase Database.
populateArrays();
// Customise UI config where necessary
setSupportActionBar(myToolbar);
mBottomBar.setDefaultSelectedIndex(2);
tag[0] = PLAN_FRAGMENT_TAG;
fragmentManager.beginTransaction().add(R.id.contentContainer, new PlanFragment(), tag[0]).commit();
// Set onClick Listener for FAB button. The FAB should change/animate as user switches between BottomBar options
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Fragment fragment;
// Find the IngredientsFragment in the Fragment Manager
fragment = fragmentManager.findFragmentByTag(INGREDIENT_FRAGMENT_TAG);
// If the Fragment exists and is visible then carryout action
if (fragment != null && fragment.isVisible()) {
Intent SelectIngredient = new Intent(getBaseContext(), Ingred_MasterList.class);
Bundle args = new Bundle();
args.putParcelableArrayList(ARG_INGREDIENTS_LIST, master);
args.putStringArrayList(ARG_STORAGE_LOCATIONS, storageLocationList);
SelectIngredient.putExtras(args);
startActivity(SelectIngredient,args);
}
}
});
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
if(getIntent().getExtras() != null) {
Bundle args = getIntent().getExtras();
if (args.containsKey(INGREDIENT_IMPORTS)) {
ingredient_imports = (args.getParcelableArrayList(INGREDIENT_IMPORTS));
bbListener.switchFragment(LOADING_FRAGMENT_TAG, new LoadingFragment());
fragmentManager.popBackStackImmediate();
distributeItems(ingredient_imports);
}
}
}
private void distributeItems(ArrayList<IngredientCard> array) {
for(IngredientCard ingredient : array){
DB_Storage_Ref.child("All").child(ingredient.getItemName()).setValue(ingredient);
DB_Storage_Ref.child(ingredient.getStorageLocation()).child(ingredient.getItemName()).setValue(ingredient);
}
ingredient_imports.clear();
}
private void populateArrays() {
// Cycle through storageLocationList array and add the storage location title (which must match a branch name on the Firebase Database.
for (int i = 0; i < storageLocationList.size(); i++) {
Bundle args = new Bundle();
args.putString(TEMP_BUNDLE_STORAGE_TITLE, storageLocationList.get(i));
// For each storage location create a loader to retrieve its data from the Firebase Database
getSupportLoaderManager().initLoader(i, args, this);
}
// Create a loader that retrieves the master list of food icons
getSupportLoaderManager().initLoader(MASTER_LIST_ARRAY_ID, null, this);
}
#Override
public Loader<ArrayList> onCreateLoader(int id, Bundle args) {
String DBbranch;
if (args == null) {
//If bundle args don't exist assume we want data from 'Master' branch of DB
DBbranch = "Food_Items";
return new IngredientsListLoader(this, DB_Master_Ref, DBbranch, this);
} else {
//If bundle args exist, extract them and add them as IngredientListLoader variable
DBbranch = args.getString(TEMP_BUNDLE_STORAGE_TITLE);
return new IngredientsListLoader(this, DB_Storage_Ref, DBbranch, this);
}
}
#Override
// Should be called after loadInBackground has completed but seems to return earlier. The method returnResults has been created in IngredientsListLoader to deal with this.
public void onLoadFinished(Loader<ArrayList> loader, ArrayList data) {
if (loader.getId() == MASTER_LIST_ARRAY_ID) {
// if MASTER_LIST Loader set master ArrayList to data
master = data;
} else {
// cycle through each item in storageLocationList Array (the Array position -eq loader id) and replace Array in storageLocationList position with data Array
for (int i = 0; i < storageLocationList.size(); i++) {
if (loader.getId() == i) {
storageLocationMapLists.put(storageLocationList.get(i), data);
}
}
}
}
#Override
public void onLoaderReset(Loader<ArrayList> loader) {
}
#Override
public void onBackPressed() {
backButtonPressed = true;
if (fragmentManager.getBackStackEntryCount() > 0) {
Log.i("MainActivity", "popping fragment backstack");
fragmentManager.popBackStack();
} else {
Log.i("MainActivity", "nothing on backstack, calling super");
super.onBackPressed();
}
}
void bottomBarUpdate(){
Fragment currentBackStackFragment = getBackstackFragment();
if(currentBackStackFragment instanceof Ingredients_BottomBarFrag || currentBackStackFragment instanceof LoadingFragment){
mBottomBar.setSelectedIndex(0,true);
return;
}
if(currentBackStackFragment instanceof MealsFragment){
mBottomBar.setSelectedIndex(1,true);
return;
}
if(currentBackStackFragment instanceof PlanFragment){
mBottomBar.setSelectedIndex(2,true);
return;
}
if(currentBackStackFragment instanceof ShoppingFragment){
mBottomBar.setSelectedIndex(3,true);
return;
}
if(currentBackStackFragment instanceof SettingsFragment){
mBottomBar.setSelectedIndex(4,true);
return;
}
}
private Fragment getBackstackFragment(){
String fragmentTag;
if(fragmentManager.getBackStackEntryCount() > 0) {
fragmentTag = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1).getName();
}else{
fragmentTag = PLAN_FRAGMENT_TAG;
fragmentManager.beginTransaction().add(R.id.contentContainer, new PlanFragment(), tag[0]).commit();
}
return fragmentManager.findFragmentByTag(fragmentTag);
}
}
class IngredientsListLoader extends AsyncTaskLoader {
private DatabaseReference DBRef;
private String DBBranch;
private ArrayList<IngredientCard> food_Items_List = new ArrayList<>();
private MainActivity ma;
IngredientsListLoader(Context context, DatabaseReference instance, String DBBranch, MainActivity main) {
super(context);
DBRef = instance;
this.DBBranch = DBBranch;
ma = main;
forceLoad();
}
#Override
public ArrayList<IngredientCard> loadInBackground() {
food_Items_List.clear();
DBRef = DBRef.child(DBBranch);
CustomListener cl = new CustomListener(ma);
DBRef.addValueEventListener(cl);
Log.v("TAG", "Returning LIST of size " + food_Items_List.size());
return cl.returnResults();
}
}
class CustomListener implements ValueEventListener {
private ArrayList<IngredientCard> food_Items_List = new ArrayList<>();
private MainActivity ma;
CustomListener(MainActivity main){
ma = main;
}
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Iterable<DataSnapshot> children = dataSnapshot.getChildren();
food_Items_List.clear();
for (DataSnapshot child : children) {
IngredientCard ingredientCard = child.getValue(IngredientCard.class);
food_Items_List.add(ingredientCard);
Log.v("ValueEventLisenter", "Accessing Firebase!");
}
returnResults();
removeLoadingScreen();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
ArrayList<IngredientCard> returnResults() {
return food_Items_List;
}
void removeLoadingScreen(){
//If all arrays have been loaded and the ingredient_import array has been cleared...
if(ma.arraysLoaded == ma.storageLocationList.size() && ma.ingredient_imports.size() == 0) {
ma.loadingComplete = true;
// tag[0] represents the tag of the currently displayed fragment. It changes to the first parameter of the switchFragment method each time it is called.
//If the displayed fragment is the LOADING_FRAGMENT switch it out for the INGREDIENT_FRAGMENT
if (ma.tag[0] == LOADING_FRAGMENT_TAG) {
ma.bbListener.switchFragment(INGREDIENT_FRAGMENT_TAG, new Ingredients_BottomBarFrag());
}
}else{
//For each loader that completes and calls this method, the values of arraysLoaded increases until it matches the number of loaders expected to return.
ma.arraysLoaded++;
}
}
}
class CustomBottomBarSelectionListener implements OnMenuItemSelectionListener {
private MainActivity ma;
CustomBottomBarSelectionListener(MainActivity main){
ma = main;
}
#Override
public void onMenuItemSelect(#IdRes int tabId, int position, boolean fromUser) {
//if this is triggered via pressing the back button, then simply return as fragmentManager.popBackStack() will handle switching fragments.
if(ma.backButtonPressed){
ma.backButtonPressed = false;
return;
}
switch (tabId) {
case R.id.menu_ingredients:
//if items have not completed loading show loading screen
if(!ma.loadingComplete && ma.ingredient_imports.size() == 0){
switchFragment(LOADING_FRAGMENT_TAG, new LoadingFragment());
}else{
switchFragment(INGREDIENT_FRAGMENT_TAG, new Ingredients_BottomBarFrag());
}
break;
//TODO: Have RecyclerView scroll position restored when fragment comes back into view
case R.id.menu_meals:
switchFragment(MEAL_FRAGMENT_TAG, new MealsFragment());
break;
case R.id.menu_plan:
switchFragment(PLAN_FRAGMENT_TAG, new PlanFragment());
break;
case R.id.menu_groceries:
switchFragment(SHOPPING_FRAGMENT_TAG, new ShoppingFragment());
break;
case R.id.menu_settings:
switchFragment(SETTINGS_FRAGMENT_TAG, new SettingsFragment());
break;
}
}
#Override
public void onMenuItemReselect(#IdRes int i, int i1, boolean b) {
//TODO Add reselect code
}
protected void switchFragment(String fragTag, Fragment frag) {
// Sets a reference of current fragments Tag
ma.tag[0] = fragTag;
if(ma.tag[0]== LOADING_FRAGMENT_TAG){
//load LOADING_FRAGMENT but DONT add to backstack
ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).commit();
}else {
//Add every other fragment to backstack
ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).addToBackStack(ma.tag[0]).commit();
}
}
};
class CustomBackStackChangeListener implements FragmentManager.OnBackStackChangedListener{
private MainActivity ma;
CustomBackStackChangeListener(MainActivity main){
ma = main;
}
#Override
public void onBackStackChanged() {
//If BackStackChanged is triggered due to anything other than pressing the back button, return.
if(!ma.backButtonPressed){
return;
}
ma.bottomBarUpdate();
}
}
IMPROVED CODE DEMONSTRATION
(sorry, adding code in the comments is horrible so I'll do it here)
protected void switchFragment(String fragTag, Fragment frag) {
// Sets a reference of current fragments Tag
ma.tag[0] = fragTag;
if(ma.tag[0]== LOADING_FRAGMENT_TAG){
//load LOADING_FRAGMENT but DONT add to backstack
ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).commit();
}else {
Fragment fragment = ma.getSupportFragmentManager().findFragmentByTag(LOADING_FRAGMENT_TAG);
if(fragment != null && fragment.isVisible()){
ma.fragmentManager.beginTransaction().remove(fragment);
}
//Add every other fragment to backstack
ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).addToBackStack(ma.tag[0]).commit();
}
}
whenever you are switching to another fragment from that fragment which you do not want too include in the backstack you can finish that fragment first before switching.
That can be done by declaring the stating object of the fragment and giving that object its instance.
then where you are switching the fragment check with the help of the fragment name if its static object is null or not.
if its not null finish the fragment
I have an activity with bottom navigation tabs that are changing the fragments in it. When I click back and forth on those tabs, at some point it stops working. Code executes just fine as I put some logs in it. But the fragments aren't being switched.
Code is in kotlin but it's rather straight forward
fun showTabFragment(tag: String) {
val currentFragment: Fragment? = supportFragmentManager.fragments?.lastOrNull()
var fragment = supportFragmentManager.findFragmentByTag(tag)
val fragmentExists = fragment != null
if (fragment == null) {
when (tag) {
TAG_LOGBOOK -> fragment = LogbookFragment()
TAG_RECIPES -> fragment = RecipesFragment()
TAG_PROFILE -> fragment = ProfileFragment()
else -> fragment = MeetingPlacesFragment()
}
}
val transaction = supportFragmentManager.beginTransaction()
if (currentFragment != null) {
Log.i("jacek", "hiding " + currentFragment.javaClass.simpleName)
transaction.hide(currentFragment)
}
if (fragmentExists) {
Log.i("jacek", "showing " + fragment.javaClass.simpleName)
transaction.show(fragment)
} else {
Log.i("jacek", "adding " + fragment.javaClass.simpleName)
transaction.add(R.id.container, fragment, tag)
}
transaction.commit()
}
The fragments are quite heavy. I will try with some lightweight ones, but still that shouldn't be a problem in my opinion. Is there anything else I could try?
I'm using the latest support library - 25.2.0
Also I'm not interested in replacing the fragments as the point is to add crossfade animation without recreating them
You need to reuse the same instance of a fragment that you wanted to hide or show.
private fun replaceFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction().apply {
if (fragment.isAdded) {
show(fragment)
} else {
add(R.id.fmFragmentContainer, fragment)
}
supportFragmentManager.fragments.forEach {
if (it != fragment && it.isAdded) {
hide(it)
}
}
}.commit()
}
#Ali's answer is good, yet imagine if you have 5 fragments. This is another way to show/hide your fragments:
// in BaseFragment
public abstract String getTAG();
//in FragmentA, FragmentB and FragmentC
public String getTAG(){
return TAG;
}
//Activity containing the fragments
//android.support.v4.app.Fragment;
private FragmentA fragmentA; //inherited BaseFragment
private FragmentB fragmentB; //inherited BaseFragment
private FragmentC fragmentC; //inherited BaseFragment
private ConcurrentHashMap<String,BaseFragment> mapOfAddedFragments = new ConcurrentHashMap<>();
/**
* Displays fragment A
*/
private void displayFragmentA() {
displayFragment(fragmentA)
}
/**
* Displays fragment B
*/
private void displayFragmentB() {
displayFragment(fragmentB)
}
/**
* Displays fragment C
*/
private void displayFragmentC() {
displayFragment(fragmentC)
}
/**
* Loads a fragment using show a fragment
* #param fragment
*/
private void displayFragment(BaseFragment fragment){
if(!mapOfAddedFragments.containsKey(fragment.getTAG()))
mapOfAddedFragments.put(fragment.getTAG(), fragment);
showFragment(fragment.getTAG(), R.id.containerBody);
}
/**
* Displays a fragment and hides all the other ones
* #param fragmentTag is the tag of the fragment we want to display
*/
private void showFragment(String fragmentTag, #IdRes int containerViewId){
FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();
BaseFragment fragment = null;
fragment = mapOfAddedFragments.get(fragmentTag);
if(fragment != null) {
if (fragment.isAdded())
ft.show(fragment);
else { //fragment needs to be added to the frame container
ft.add(containerViewId, fragment, fragment.getTAG());
}
}
else //the chosen fragment doesn't exist
return;
//we hide the other fragments
for (ConcurrentHashMap.Entry<String, BaseFragment> entry : mapOfAddedFragments.entrySet()){
if(!entry.getKey().equals(fragmentTag)){
BaseFragment fragmentTemp = entry.getValue();
// Hide the other fragments
if(fragmentTemp != null)
if(fragmentTemp.isAdded())
ft.hide(fragmentTemp);
}
}
//commit changes
ft.commit();
}
And to instantiate them you can do this in the onCreate() method of your activity:
//don't forget to get the .TAG elsewhere before using them here
//never call them directly
private void instantiateFragments(Bundle inState) {
if (inState != null) {
fragmentA = inState.containsKey(FragmentA.TAG) ?
(FragmentA) getSupportFragmentManager().getFragment(inState, FragmentA.TAG):
FragmentA.newInstance(FragmentA.TAG,"0");
fragmentB = inState.containsKey(FragmentB.TAG) ?
(FragmentB) getSupportFragmentManager().getFragment(inState, FragmentB.TAG):
FragmentB.newInstance(FragmentB.TAG,"1");
fragmentc = inState.containsKey(FragmentC.TAG) ?
(FragmentC) getSupportFragmentManager().getFragment(inState, FragmentC.TAG):
FragmentC.newInstance(FragmentC.TAG,"2");
}
else{
fragmentA = FragmentA.newInstance(FragmentA.TAG,"0");
fragmentB = FragmentB.newInstance(FragmentB.TAG,"1");
fragmentc = FragmentC.newInstance(FragmentC.TAG,"2");
}
}
Edit according to Shujaat Ali Khan's question:
The BaseFragment extends support4 fragment:
public abstract class BaseFragment extends Fragment {
public abstract String getTAG();
//whatever we can add to be inherited
}
FragmentA for example:
public class FragmentA extends BaseFragment {
// Store instance variables
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
public static final String TAG = "FragmentA";
// newInstance constructor for creating fragment with arguments
public static FragmentA newInstance(String param1, String param2) {
FragmentA fragment = new FragmentA();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
// Store instance variables based on arguments passed
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
// Inflate the view for the fragment based on layout XML
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentA, container, false);
return view;
}
//other lifecycle methods
#Override
public String getTAG() {
return TAG;
}
}
Finally the R.id.containerBody is the id of a FrameLayout containing the fragments in the activity containing these fragments.
The problem here is even though you're hiding "current" fragment, there are other fragments loaded in the memory and that gives inconsistent behaviour.
You should be able to fix this by hiding all the fragment except the fragment you want to show.
Thanks to this answer. Show hide fragment in android
eg:
private FragmentA fragmentA;
private FragmentB fragmentB;
private FragmentC fragmentC;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentA = FragmentA.newInstance();
fragmentB = FragmentB.newInstance();
fragmentC = FragmentC.newInstance();
}
protected void displayFragmentA() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (fragmentA.isAdded()) {
ft.show(fragmentA);
} else {
ft.add(R.id.fragement_container, fragmentA);
}
if (fragmentB.isAdded()) { ft.hide(fragmentB); }
if (fragmentC.isAdded()) { ft.hide(fragmentC); }
ft.commit();
}
Similarly you will have to write functions for displayFragmentB() and displayFragmentC()
How to return a value from Asynctask (Different class) to activity back from which you called the Asynctask , here i have followed the intsruction given in following link How to get the result of OnPostExecute() to main activity because AsyncTask is a separate class? #HelmiB's
I have done everything and its returning the result to activity's method processFinish() also , the problem is lost the activity control or focus, i could not do further actions using the Asynctask's result, as because all my activity's members becomes null.
How to proceed ?
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if (pDialog != null)
{
pDialog.cancel();
}
if (StringUtil.hasValue(responseXmlString))
{
if (Integer.parseInt(AppUtil.getXpathValue("Result/ErrorNo",AppUtil.buildDocument(responseXmlString))) == 0)
{
asyncResponse.processFinish(responseXmlString);
}
}
#Override
public void processFinish(Object output)
{
Log.d("Response From Asynchronous task:", (String) output);
displayView(position,(String) output);
}
private void displayView(int position,String responseXml)
{
// update the main content by replacing fragments
boolean isFragment = true;
Fragment fragment = null;
/*//For Handling back press
if (getIntent().getBooleanExtra("FromPassBook", false) )
{
getIntent().putExtra("FromPassBook", false);
position = 7;
}
if (getIntent().getBooleanExtra("toCustomerAcocunts", false) )
{
getIntent().putExtra("toCustomerAcocunts", false);
position = 1;
}*/
switch (position)
{
case 0:
fragment = new ChartFragment();
setTitle(getResources().getString(R.string.wealth));
break;
default:
break;
}
if (fragment != null && isFragment)
{
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction ft =fragmentManager.beginTransaction();
fragmentStack.push(fragment);
//passing data to fragment
if (StringUtil.hasValue(responseXml))
{
Bundle bundle = new Bundle();
bundle.putString("responseXml", responseXml);
fragment.setArguments(bundle);
}
ft.replace(R.id.frame_container, fragment).commit();
// update selected item and title, then close the drawer
listView.setItemChecked(position, true);
listView.setSelection(position);
mDrawerLayout.closeDrawer(listView);
}
else
{
// error in creating fragment
Log.e("MainActivity", "Error in creating fragment");
}
}
private void callService(int position)
{
String input = "";
AsyncCallWS asyncCallWS;
switch (position)
{
case 0:
input = "<Parm><ProcessID>101</ProcessID><MobileNo>" + mobileNumber + "</MobileNo></Parm>";
asyncCallWS = new AsyncCallWS(MainActivity.this, input, position,new MainActivity());
asyncCallWS.execute();
break;
}
Asynctask class constructor
`AsyncResponse asyncResponse;
public AsyncCallWS(Context context,String input,int position,AsyncResponse response )
{
this.context = context;
this.inputToservice = input;
this.position = position;
asyncResponse = response;
}`
Well, your problem is in this line of code:
asyncCallWS = new AsyncCallWS(MainActivity.this, input, position,new MainActivity());
Or, particularly, in the statement new MainActivity(). You create a new instance of MainActivity class here and then you use it as a callback. Obviously, it will have all the fields non-initialized. Use MainActivity.this instead of new MainActivity(). And please remember that Android manages all of your activities. You never want to create one yourself.
I have a ViewPager that I am populating with fragments(representing objects from arrayListOfObjects) using FragmentStatePagerAdapter.
All works well:
mMyFragmentPagerAdapter = new fragmentAdapter(getSupportFragmentManager(),orientation
,rePopulatedfireInfoList);
mPager = (ViewPager)findViewById(R.id.fireInfoFragment_container);
initButton();
setTab();
mPager.setAdapter(mMyFragmentPagerAdapter);
The fragment adapter extends FragmentStatePagerAdapter.
From the primary activity I launch a dialog themed activity; where the user may add a new favourite location creating a new object which alters the arraylist of objects passed by the primary activity.
This is the code for starting dialog activity; all works fine:
Intent locationIntent = new Intent(afisController.this, locationActivity.class);
locationIntent.putExtra("firesList", new fireInfoListWrapper(arrayListOfObjects));
startActivityForResult(locationIntent,1);
The floating activity adds objects into arrayListOfObjects.
On the primary activity's onActivityResult I compare the arraListOfObjects I'm receiving with the one I sent; if different I want to completely remove the contents of the viewPager and recreate it with the new arrayListOfObjects. This is the onActivityResults:
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
Toast.makeText(this, "Activity Results fired..." , 1500 ).show();
if ((resultCode == 0) && (data != null)) {
Log.i("onActvityResult", "Inside resultCode check");
Bundle b = data.getExtras();
if(b != null){
Log.i("onActvityResult", "not null");
returnedFireInfoList = (ArrayList<fireInfo>) data.getSerializableExtra("firesListResult");
Log.i("onActvityResult", "results Size: "+returnedFireInfoList.size());
if(returnedFireInfoList.size()>0){
Log.i("onActvityResult", "locationName: "+returnedFireInfoList.get(0).getLocationName());
//compare returnedFireInfoList and rePopulatedfireInfoList, if different;
//add difference to rePopulatedfireInfoList and write back to file.
updateFireInfos(returnedFireInfoList, rePopulatedfireInfoList);
if(returnedFireInfoList.size()!=rePopulatedfireInfoList.size()){
mMyFragmentPagerAdapter1 = new fragmentAdapter(getSupportFragmentManager(),orientation
,returnedFireInfoList);
mPager = (ViewPager)findViewById(R.id.fireInfoFragment_container);
Log.i("updateFireInfos", "fragmentsCount is"+mPager.getCurrentItem());
fireInfoFragment fragment =
(fireInfoFragment) getSupportFragmentManager().findFragmentById(R.id.fireInfoFragment_container);
//This is where the problem is, I don't want to remember what was already on the viewPager //called mPager before.
// mPager.removeAllViews();
//mPager.setAdapter(null);
mMyFragmentPagerAdapter1.notifyDataSetChanged();
mPager.setAdapter(mMyFragmentPagerAdapter1); mMyFragmentPagerAdapter1.notifyDataSetChanged();
}
}
}
This is the fragmentStateAdapter code:
public class fragmentAdapter extends FragmentStatePagerAdapter {
private FragmentManager fragmentManager;
private FragmentTransaction mCurTransaction = null;
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
public void restoreState(Parcelable state, ClassLoader loader) {
//Need to only delete info from marked fragments (theoned that are stored on orientationchange
//Currently redoing the entire call; resulting in delay due to server call
//if(isLastOrientationPortrait != isPortrait){
if(state != null){
Bundle bundle1 = (Bundle) state;
bundle1.setClassLoader(loader);
Iterable<String> keys = bundle1.keySet();
Log.i("restoreState", "containsKey FragmentStatePagerAdapter: "+keys);
//android.support.v4.app.FragmentManager fragmentManager= fragmentAdapter.this.fragmentManager;
android.support.v4.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
//if (fragmentTransaction == null) {
// Log.i("restoreState", "fragmentTransactionTest");
fragmentTransaction = fragmentManager.beginTransaction();
// }
for (String key : keys) {
if (key.startsWith("f")) {
Fragment f = fragmentManager.getFragment(bundle1,
key);
fragmentTransaction.remove(f);
fragmentTransaction.commit();
}
}
}
//}
}
#Override
public int getItemPosition(Object object) {
// TODO Auto-generated method stub
//return super.getItemPosition(object);
return fragmentAdapter.POSITION_NONE;
}
public fragmentAdapter(android.support.v4.app.FragmentManager fragmentManager,String orientation,
ArrayList<fireInfo> fireInfoList) {
super(fragmentManager);
this.orientation = orientation;
this.fireInfoList = fireInfoList;
this.numItems = fireInfoList.size();
this.fragmentManager=fragmentManager;
}
ArrayList<fireInfo> fireInfoList;
String orientation;
int numItems;
#Override
public int getCount() {
Log.i("numItems", "is: "+fireInfoList.size());
return numItems;
}
#Override
public Fragment getItem(int arg0) {
Log.i("fragmentAdapterIndex", "is: "+arg0);
return fireInfoFragment.newInstance(orientation, fireInfoList.get(arg0));
}
}
Problem:
But the new ArrayListOfObjects is added alongside the old one before I fired the startActivityFor results.
How do I force the viewPager to forget it old content? basically reset the viewPager adapter with this newArrayListofObjects using my fragmentStateAdapter?
I guess the problem is in the fact that old fragments still reside in FragmentManager you use for your adapter. If this is the case all you have to do is remove all old fragments from the fragment manager.
So basically just execute the following code in the constructor of your adapter:
public fragmentAdapter(FragmentManager fragmentManager, String orientation, ArrayList<fireInfo> list) {
super(fragmentManager);
if (fragmentManager.getFragments() != null) {
fragmentManager.getFragments().clear();
}
//... your other code here
}
This line of code is unnecessary:
mMyFragmentPagerAdapter1.notifyDataSetChanged();
EDIT: It may be more correct to remove your fragments using FragmentTransaction:
List<Fragment> fragments = fragmentManager.getFragments();
if (fragments != null) {
FragmentTransaction ft = fragmentManager.beginTransaction();
for (Fragment f : fragments) {
//You can perform additional check to remove some (not all) fragments:
if (f instanceof AddedByCurrentPagerAdapterFragment) {
ft.remove(f);
}
}
ft.commitAllowingStateLoss();
}
This will take some time for FragmentTransaction to be (asynchronously) performed.
All I needed to do was to re-assign the FragmentStatePagerAdapter.
This has been in place as can be seen in onActivityResult but what was masking the correct behaviour was my viewPager page indicator it was incrementing the pages e.g. if I had 2 pages on the viewPager and call the child actvity which will add one object on the arrayListOfObjects; the viewPager page indicator would show that I now have 2 plus three pages (5).
I had to reset the viewPage indicator in onActivityResult to have it determined by this new arrayListOfObjects just returned by the floating activity.