AlexF11 AlexF11 - 5 months ago 27
Android Question

How can I populate fragments with data that aren't available until after the fragment's creation?

I have an activity that grabs data via

WebService
, from there it creates elements to display the data. Some data is grouped so my solution was to display the grouped data in their own
fragments
below the main layout, allowing the user to swipe across the groups, probably with a tabs at the top to show the group name.

The problem I came across was that the fragments in the activity are created before that web call takes place, making them empty or using old data. I then created a
sharedpreferences
listener and placed the
fragments
layout creation method within it. The main method grabs the data, writes to sharedpreferences the fragment detects the change and creates it's layout, Or so I thought.

Some groups are the same between items, so moving from one to the other won't trigger that onchange event thus not triggering the layout creation method. I then decided to do the following to always trigger the onchange event after the sharedpreferences are written

final Boolean updated = settings.getBoolean("UPDATED_1", false);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("UPDATED_" + pageNum, !updated);


I just don't think that's the best solution, it also has it's problems and isn't triggering every time (Which I have yet to troubleshoot)

What's a better solution for all this? I also have a memory leak I haven't diagnosed yet to make things even more of a headache.

I've just thought of moving my data grabbing method to before the
ViewPager
initialization but I'm not yet sure if this will solve my problem.

Answer

I would not recommend waiting until you get the data to show the view as it will affect the User Experience and look sluggish.

Instead, you could implement an AsyncTaskLoader in your fragment so you can inform the Fragment's View with a BroadcastReceiver once you get the data from your server. In the meantime, just show a spinner until the data are retrieved, then you hide it and update your list with a adapter.notifyDataSetChanged();.

Here is an example of a AsyncTaskLoader (In my case it's a database query instead of a server call like you):

public class GenericLoader<T extends Comparable<T>> extends AsyncTaskLoader<ArrayList<T>> {
    private Class clazz;

    public GenericLoader(Context context, Class<T> clazz) {
        super(context);
        this.clazz = clazz;
    }

    @Override
    public ArrayList<T> loadInBackground() {
        ArrayList<T> data = new ArrayList<>();
        data.addAll(GenericDAO.getInstance(clazz).queryForAll());
        Collections.sort(data);
        return data;
    }
}

Then in your Fragment:

public class FragmentMobileData extends Fragment implements ListAdapter.OnItemClickListener, LoaderManager.LoaderCallbacks<ArrayList<EntityCategories.EntityCategory>> {

    public static String TAG = "FragmentMobileData";

    private ImageListAdapter adapter;
    private ArrayList<EntityList> mCategories = new ArrayList<>();

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Bundle bundle = intent.getExtras();
            String result = bundle.getString(DatabaseService.RESULT);

            if (DatabaseService.NO_CONNECTION.equals(result)) {
                Utils.showToastMessage(getActivity(), "No internet connexion", true);
            } else if (DatabaseService.RESULT_TIMEOUT.equals(result)) {
                Utils.showToastMessage(getActivity(), "Bad connection. Retry", true);
            }
            getActivity().getSupportLoaderManager().initLoader(1, null, FragmentMobileData.this).forceLoad();
        }
    };

    @Bind(R.id.progressBarEcard)
    ProgressBar spinner;
    @Bind(R.id.list)
    RecyclerView list;
    public FragmentMobileData() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_mobile_plan, container, false);
        ButterKnife.bind(this, view);
        ((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("Mobile");

        list.setLayoutManager(new LinearLayoutManager(context));
        list.addItemDecoration(new DividerItemDecoration(context, R.drawable.divider));
        adapter = new ImageListAdapter(mCategories, this);
        list.setAdapter(adapter);

        Intent intent = new Intent(context, DatabaseService.class);
        intent.setAction(DatabaseService.UPDATE_DATA);
        getActivity().startService(intent);
        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        getActivity().unregisterReceiver(mReceiver);
    }


    @Override
    public void onResume() {
        super.onResume();
        getActivity().registerReceiver(mReceiver, new IntentFilter(DatabaseService.UPDATE_DATA));
    }

    @Override
    public Loader<ArrayList<EntityCategories.EntityCategory>> onCreateLoader(int id, Bundle args) {
        return new GenericLoader(context, EntityCategories.EntityCategory.class);
    }

    @Override
    public void onLoadFinished(Loader<ArrayList<EntityCategories.EntityCategory>> loader, ArrayList<EntityCategories.EntityCategory> data) {
        if (mCategories.size() != data.size()) {
            mCategories.clear();
            mCategories.addAll(data);
            adapter.notifyDataSetChanged();

            Intent intent = new Intent(context, DownloadFilesService.class);
            context.startService(intent);
        }
        spinner.setVisibility(View.GONE);
    }

    @Override
    public void onLoaderReset(Loader<ArrayList<EntityCategories.EntityCategory>> loader) {
        mCategories.clear();
        adapter.notifyDataSetChanged();
    }
 //...
}