0

I am trying to implement Instagram-like user profile screen. This is my flow ProfileFragment --> SingleImageFragment --> ProfileFragment --> SingleImageFragment. So, the user can have multiple instances of the same fragment. I don't want to hide/show because it costs too much memory. So I am using

mFragment = fragment;
    mFragmentManager
            .beginTransaction()
            .replace(R.id.frame_container, fragment, Integer.toString(getFragmentCount()))
            .addToBackStack(null)
            .commit();

But, this way ProfileFragment goes into onDestroyView and when it is returned from backStack the list will be loaded again since it goes to onViewCreated method. I am trying to find a way how to not load the data that has already been loaded when coming to that fragment. Is there a way that I can save the already loaded list and use that one in onViewCreated?

This is ProfileFragment:

    public class ProfileFragment extends BaseChildFragment implements ProfileAdapter.OnHeaderItemClickedListener,
        SwipeRefreshLayout.OnRefreshListener {

    public static final String ACTION_SINGLE_IMAGE = ProfileFragment.class.getName() + ".single_image";
    public static final String ACTION_SETTINGS = ProfileFragment.class.getName() + ".settings";
    private static final String ARG_PARAM1 = "param1";

    FragmentProfileBinding mBinder;
    FragmentManager mFragmentManager;
    Home mHome;
    Home tHome;
    List<Home> mHomeList;
    ProfileAdapter mAdapter;
    StaggeredGridLayoutManager mManager;
    EndlessRecyclerViewScrollListener mEndlessScrollListener;
    PreferenceAdapter mPreferenceAdapter;
    int mInstance;

    public ProfileFragment() {

    }

    public static ProfileFragment newInstance(int instance) {
        ProfileFragment fragment = new ProfileFragment();
        Bundle args = new Bundle();
        args.putInt(ARGS_INSTANCE, instance);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mInstance = getArguments().getInt(ARGS_INSTANCE);
        }
        mHomeList = new ArrayList<>();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mBinder = DataBindingUtil.inflate(inflater, R.layout.fragment_profile, container, false);
        mFragmentManager = getChildFragmentManager();
        mHome = new Home();
        tHome = new Home();
        mPreferenceAdapter = new PreferenceAdapter(getContext());
        mManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
        mAdapter = new ProfileAdapter(getContext(), mHomeList);
        mBinder.rvGrid.setLayoutManager(mManager);
        mBinder.rvGrid.setAdapter(mAdapter);
        mAdapter.addOnHeaderItemClickListener(this);
        mBinder.srLayout.setOnRefreshListener(this);
        mEndlessScrollListener = new EndlessRecyclerViewScrollListener(mManager) {
            @Override
            public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
                populateList();
                populateSecondList();
                mAdapter.notifyDataSetChanged();
            }
        };
        mBinder.rvGrid.addOnScrollListener(mEndlessScrollListener);
        setUIListeners();
        return mBinder.getRoot();
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        populateList();
        populateSecondList();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mAdapter.removeOnHeaderItemClickListener();
        mBinder.rvGrid.removeOnScrollListener(mEndlessScrollListener);
    }

    private void setUIListeners() {
    }

    @Override
    public void onGridImageClicked(Home home) {
        sendActionToParent(ACTION_SINGLE_IMAGE);
    }

    @Override
    public void onRefresh() {
        mHomeList.clear();
        populateList();
        populateSecondList();
        mAdapter.notifyDataSetChanged();
        mEndlessScrollListener.resetState();
        mBinder.srLayout.setRefreshing(false);
    }

    private void sendActionToParent(String action) {
        if (mParentListener == null) {
            return;
        }
        Bundle bundle = new Bundle();
        bundle.putString(Constants.ACTION_KEY, action);
        mParentListener.onChildFragmentInteraction(bundle);
    }

    private void sendActionToActivity(String action) {
        if (mActivityListener == null) {
            return;
        }
        Bundle bundle = new Bundle();
        bundle.putString(Constants.ACTION_KEY, action);
        mActivityListener.onChildFragmentToActivityInteraction(bundle);
    }

    private void populateList() {
        for (int i = 0; i < 5; i++) {
            mHome.setType(0);
            mHomeList.add(i, mHome);
        }
    }

    private void populateSecondList() {
        for (int i = 0; i < 5; i++) {
            tHome.setType(1);
            mHomeList.add(i, tHome);
        }
    }
}
Esteban
  • 667
  • 9
  • 22

3 Answers3

0

Yes, you can try to use setRetainInstance(true); (add this line in your onCreate() method) to retain the state of your Fragment.

Or you can try to use SQLite or an ORM Framework to persist the list into a database. I advise you to use ORMLite to save your list:

To use ORMLite in your project, add this line in your gradle.build file (module:app)

compile group: 'com.j256.ormlite', name: 'ormlite-android', version: '4.45'

And then, you have to create a wrapper class and put your list inside it:

@DatabaseTable(tableName = "your_table_name")
public class YourClass{

@DatabaseField(generatedId = true)
private int id;

@DatabaseField(dataType = DataType.SERIALIZABLE)
private List<YourList> list;

//getters and setters here

}

Now, you have to create your DatabaseHelper:

public class DatabaseHelper extends OrmLiteSqliteOpenHelper {

    // name of the database file for your application -- change to something appropriate for your app
    private static final String DATABASE_NAME = "yourdatabasename.db";
    // any time you make changes to your database objects, you may have to increase the database version
    private static final int DATABASE_VERSION = 1;

    // the DAO object we use to access the SimpleData table
    private Dao<YourClass, Integer> simpleDao = null;
    private RuntimeExceptionDao<YourClass, Integer> simpleRuntimeDao = null;

    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION, R.raw.ormlite_config);
    }

    /**
     * This is called when the database is first created. Usually you should call createTable statements here to create
     * the tables that will store your data.
     */
    @Override
    public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
        try {
            Log.i(DatabaseHelper.class.getName(), "onCreate");
            TableUtils.createTable(connectionSource, SimpleData.class);
        } catch (SQLException e) {
            Log.e(DatabaseHelper.class.getName(), "Can't create database", e);
            throw new RuntimeException(e);
        }

        // here we try inserting data in the on-create as a test
        RuntimeExceptionDao<YourClass, Integer> dao = getSimpleDataDao();
        long millis = System.currentTimeMillis();
        // create some entries in the onCreate
        YourClass simple = new YourClass();
        dao.create(simple);
        simple = new YourClass();
        dao.create(simple);
        Log.i(DatabaseHelper.class.getName(), "created new entries in onCreate: " + millis);
    }

    /**
     * This is called when your application is upgraded and it has a higher version number. This allows you to adjust
     * the various data to match the new version number.
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
        try {
            Log.i(DatabaseHelper.class.getName(), "onUpgrade");
            TableUtils.dropTable(connectionSource, SimpleData.class, true);
            // after we drop the old databases, we create the new ones
            onCreate(db, connectionSource);
        } catch (SQLException e) {
            Log.e(DatabaseHelper.class.getName(), "Can't drop databases", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Returns the Database Access Object (DAO) for our SimpleData class. It will create it or just give the cached
     * value.
     */
    public Dao<YourClass, Integer> getDao() throws SQLException {
        if (simpleDao == null) {
            simpleDao = getDao(SimpleData.class);
        }
        return simpleDao;
    }

    /**
     * Returns the RuntimeExceptionDao (Database Access Object) version of a Dao for our SimpleData class. It will
     * create it or just give the cached value. RuntimeExceptionDao only through RuntimeExceptions.
     */
    public RuntimeExceptionDao<SimpleData, Integer> getSimpleDataDao() {
        if (simpleRuntimeDao == null) {
            simpleRuntimeDao = getRuntimeExceptionDao(SimpleData.class);
        }
        return simpleRuntimeDao;
    }

    /**
     * Close the database connections and clear any cached DAOs.
     */
    @Override
    public void close() {
        super.close();
        simpleDao = null;
        simpleRuntimeDao = null;
    }

And finally, save your list and recover it when you want:

//To query all:
Yourclass class = simpleDao.queryForAll();

//To save data:
simpleDao.create(your_object_whit_lists);
Luiz Fernando Salvaterra
  • 4,192
  • 2
  • 24
  • 42
  • With `setRetainInstance(true)` the lifecycle goes to `onDestroyView` when I go to another fragment. When I come back to it, from `backStack`, it goes into `onViewCreated` again, so my list is never saved. – Esteban Apr 25 '17 at 12:09
  • in this case, you have to use the 2 option, use a SQLite or ORMLite – Luiz Fernando Salvaterra Apr 25 '17 at 12:12
  • Hmm but then I would have to store separate lists for each fragment and delete them when the fragment is destroyed. Because I might have 4 instances of `ProfileFragment`, each of them with different list. – Esteban Apr 25 '17 at 12:32
0

Use savedInstanceState for the example

  1. Make your 'Home' pojo class implement Parcelable.
  2. Change List mHomeList to ArrayList mHomeList.
  3. Implement onSaveInstanceState(Bundle outState) on your fragment and put mHomeList to sate bundle.

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelableArrayList("STATE_HOME_LIST", mHomeList);
    }
    
  4. In onViewCreated() you can restore your mHomeList like this

    if (savedInstanceState != null) {
        mHomeList = savedInstanceState.getParcelableArrayList("STATE_HOME_LIST");
        //  do something // update Adapter
    }else {
        populateList();
        populateSecondList();
    }
    
Community
  • 1
  • 1
JingJoeh
  • 393
  • 4
  • 14
  • By replacing the fragments in the same activity, the fragment never calls `onSaveInstanceState`. I need to save the list as `parcelable` but where do I save it if `onSaveInstanceState` is never called? – Esteban Apr 25 '17 at 12:31
0

Based on the second answer of this question, I've come up with this solution. I don't know how reliable it's going to be though.

I save the list in Bundle:

private Bundle saveState() {
    Bundle state = new Bundle();
    Parcelable parcelable = Parcels.wrap(mHomeList);
    state.putParcelable("homeList", parcelable);
    return state;
}

And then in onViewCreated:

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    if (savedState != null) {
        mHomeList = Parcels.unwrap(savedState.getParcelable("homeList"));
    } else {
        populateList();
        populateSecondList();
    }
    savedState = null;
    Log.d("TAG", "PROFILE: " + mHomeList.size());
}
Community
  • 1
  • 1
Esteban
  • 667
  • 9
  • 22