0

Recently i'm working on my app to make it load faster and work better, i'm using navigation drawer in my MainActivity:

    @Override
public boolean onNavigationItemSelected(MenuItem item) {

    // Handle navigation view item clicks here.
    int id = item.getItemId();

    if (id == R.id.nav_camara) {
        LoadJson asyncTask = (LoadJson) new LoadJson(new LoadJson.AsyncResponse() {
            @Override
            public void processFinish(JSONArray output) {
                //Here you will receive the result fired from async class
                //of onPostExecute(result) method.
                //Set the fragment initially
                MainFragment fragment = new MainFragment(output);
                FragmentTransaction fragmentTransaction =
                        getSupportFragmentManager().beginTransaction();
                fragmentTransaction.add(R.id.fragment_container, fragment);
                fragmentTransaction.commit();
                // Handle the camera action
            }
        }).execute();


    } else if (id == R.id.nav_gallery) {
        //Set the fragment initially
        GalleryFragment fragment = new GalleryFragment();
        FragmentTransaction fragmentTransaction =
                getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.fragment_container, fragment);
        fragmentTransaction.commit();


    } else if (id == R.id.nav_search) {
        //Set the fragment initially
        FetchResualt fragment = new FetchResualt();
        FragmentTransaction fragmentTransaction =
                getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.fragment_container, fragment);
        fragmentTransaction.commit();
        // Handle the camera action

    } else if (id == R.id.nav_manage) {//
         Bundle bundle = new Bundle();//
        bundle.putInt("someStr",ID_OF_BEACH);//
        //Set the fragment initially//
        FragmentBeach fragment = new FragmentBeach();//
        fragment.setArguments(bundle);//
        FragmentTransaction fragmentTransaction =//
                getSupportFragmentManager().beginTransaction();//
        fragmentTransaction.replace(R.id.fragment_container, fragment);//
        fragmentTransaction.commit();//
        // Handle the camera action

    } else if (id == R.id.nav_share) {

    } else if (id == R.id.nav_send) {

    }

    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    drawer.closeDrawer(GravityCompat.START);
    return true;
}

As you can see if we push the first menu if (id == R.id.nav_camara) it will pass a JSONArray to a Fragment class and it take like 4-5 seconds to load. So if the user navigates between menus every time he goes to nav_camara it make the app a 4 second freeze to load and as you can see everytime we choose a menu item it will recreate a new copy of fragment, so even if I make: setRetainInstance(true); in my fragment class it wont work.

What solution you suggest to prevent the app to recreate a new fragment each time we choose a menu item?

Sufian
  • 6,405
  • 16
  • 66
  • 120
Blacksword
  • 331
  • 3
  • 17
  • Maybe the loading is the problem? And not the fragment itself? Try to implement some prefetching and execute your LoadJson task earlier. – Christopher Sep 06 '16 at 09:23
  • hey just visit [this link](http://stackoverflow.com/questions/14839152/fragment-as-a-singleton-in-android). Hope here you can find something useful. – Rahul Sharma Sep 06 '16 at 09:25
  • i don't think it makes any difference because there are some calculations to make culester markers and iterate the json file and put it on map, this take long and each time the app make a new copy of fragment these calculations will happen it think the solution can be not receate the fragment – Blacksword Sep 06 '16 at 09:34
  • This is not Navigation Drawer. It is something else. A navigation drawer can be brought up by making a swipe/fling gesture from the start of the screen (usually left), or by pressing the hamburger button (in the ActionBar). And it is only created/restored during Activity's `onCreate()` lifecycle method. What you're doing is clearly something else. – Sufian Sep 06 '16 at 09:55
  • @Sufian it does exactly the same but i define an ID for each menu item so i implement my fragment when one of them choosed – Blacksword Sep 06 '16 at 09:58
  • @AryaR. sorry, I misread. The delay is due to creating the Fragments in each case, like `new FetchResualt()` or `new GalleryFragment()` will take time to fetch its data (from API/internet) and then after that the Fragment will be shown. You want to avoid creating it again and again, correct? – Sufian Sep 06 '16 at 10:10
  • @Sufian that's one of the points but i want when the map loads, by changing to another fragment and come back again dont reload and recalculate all the things from begining. – Blacksword Sep 06 '16 at 10:27
  • @AryaR. okay you want it like this: when a user clicks `nav_gallery`, a Fragment (if not created before), it is returned; but if it was created before (i.e old instance exists), it should be returned instead. Is it so? – Sufian Sep 06 '16 at 11:44
  • @Sufian more or less yes this statement can be happen in one condition: if first you be able to save your fragment in cache or somewhere – Blacksword Sep 06 '16 at 11:53

3 Answers3

2

You will need to keep a SparseArray<Fragment> to keep the instances in memory.

Follow these steps:

  1. create a field in your Activity:

    SparseArray<Fragment> myFragments;
    
  2. initialise it in the onCreate() like:

    myFragments = new SparseArray<Fragment>();
    
  3. update your onNavigationItemSelected():

    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();
    
        if (id == R.id.nav_camara) {
            // get cached instance of the fragment
            fragment = myFragments.get(INT_CONSTANT_FOR_CAM_FRAGMENT);
    
            // if fragment doesn't exist in myFragments, create one and add to it
            if (fragment == null) {
                fragment = new MainFragment();
                myFragments.put(INT_CONSTANT_FOR_CAM_FRAGMENT, fragment);
            }
    
            // now load the fragment
            FragmentTransaction fragmentTransaction =
                        getSupportFragmentManager().beginTransaction();
            fragmentTransaction.replace(R.id.fragment_container, fragment);
            fragmentTransaction.commit();
    
        }
        // do the rest with others as well.
    }
    
  4. move the AsyncTask thing inside your MainFragment's onActivityCreated() or a similar lifecycle method.

Above way will let you reuse the fragments and move the logic to their correct location (your Activity shouldn't know how MainFragment loads data to initiate itself, but of course you can still keep it there, though not recommended).

Sufian
  • 6,405
  • 16
  • 66
  • 120
  • here: fragmentTransaction.add(R.id.fragment_container, fragment); what is the value of 'fragment' ? i think you forgot to instantiate it – Blacksword Sep 06 '16 at 13:23
  • @AryaR. fixed that. – Sufian Sep 06 '16 at 13:32
  • i try the code like this: int id = item.getItemId(); if (id == R.id.nav_camara) { //Set the fragment initially Fragment fragment = myFragments.get(id, new MainFragment()); // now load the fragment FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit(); it doesnt work :( – Blacksword Sep 06 '16 at 13:40
  • @AryaR. explain what you mean by "it doesnt work". Does it do nothing or it crashes or something else? – Sufian Sep 06 '16 at 14:52
  • it means it doesnt resolve the problem it works as before reload again – Blacksword Sep 06 '16 at 17:32
  • @AryaR. fixed that. I wasn't putting the newly created instance in the `myFragments`. It should work now. If it doesn't, let me know. – Sufian Sep 07 '16 at 01:47
  • ok it worked for a simple form but for the map that im using in my app it reloads again – Blacksword Sep 07 '16 at 07:45
  • @AryaR. haven't worked with maps but I guess you might be able to achieve it using the good old `onSaveInstanceState()`. It is a rather different issue as has been discussed in other thread. See [this thread](http://stackoverflow.com/questions/16236439/restoring-map-state-position-and-markers-of-google-maps-v2-on-rotate-and-on). – Sufian Sep 07 '16 at 08:19
  • there one more problem when i use: fragmentTransaction.add after i come back to fragment it gave error and jump out, but when i use replace instead of add it works what you think? – Blacksword Sep 07 '16 at 13:25
  • @AryaR. why do you use `.add()`? I use `.replace()` and it works fine. – Sufian Sep 07 '16 at 13:45
  • so edit the above code you used ádd´. why this solution doesn't work on map? – Blacksword Sep 07 '16 at 13:47
  • Updated that. Have you followed [this thread](http://stackoverflow.com/questions/16236439/restoring-map-state-position-and-markers-of-google-maps-v2-on-rotate-and-on)? – Sufian Sep 07 '16 at 13:50
  • @AryaR. did that crash stop now by replacing `.add()` with `.replace()`? If not, could you share the LogCat? – Sufian Sep 08 '16 at 07:29
0

You can add a TAG while push a new fragment to the container. Then use FragmentManager#findFragmentByTag to find any fragment previously added with the same tag.

        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        Fragment oldFragment = manager.findFragmentByTag(tag);
        if (oldFragment != null)
            transaction.replace(R.id.fragment_container, oldFragment, tag);
        else
            transaction.replace(R.id.fragment_container, newInstanceOfFragment, tag);
        transaction.commit();
Alex Chengalan
  • 8,211
  • 4
  • 42
  • 56
0

Declare them globally

GalleryFragment galleryFrag = new GalleryFragment();
FragmentTransaction ft;
FragmentManager fm;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    fm = getSupportFragmentManager();

    ...
}

then on your navigation selection

@Override
public boolean onNavigationItemSelected(MenuItem item) {

// Handle navigation view item clicks here.
int id = item.getItemId();

if (id == R.id.nav_search) {
    ft = fm.beginTransaction();
    ft.replace(R.id.fragment_container, galleryFrag).commit();
} 

DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}