1

I want to make an app lister application which fetches the application list via the packagemanager through an AsyncTask<Void, Void, List<PackageSummary>> nested in a singleton class. However, if and only if I implement the async task, the RecyclerView will not populate on the first OnCreate.

I am sure I am doing a silly mistake and/or do not understand AsyncTask and RecyclerView well enough, but for the love of me I cannot find the root of the issue.

In my toy app repository I have prepared two, relatively cleaner branches for illustration purposes:

  • One in which the packages are fetched in the main thread, and the recyclerview populates on first Oncreate (git_UI_thread).
  • One in which an AsyncTask<Void, Void, List<PackageSummary>> class is called. The application persistence is not set yet (on purpose), and the RecyclerView will only populate after the application is rotated (git_background_thread).

For those who are not inclined to click on the bitbucket link above, the code snippet of the inside of my AsyncTask looks like this:

    @Override
    protected void onPostExecute(List<SingletonPackageSummarySupplier.PackageSummary> packageSummaries) {
        super.onPostExecute(packageSummaries);
        isQueryingInProgress = false;
        packageSummaryList = packageSummaries;
    }

    @Override
    protected List<SingletonPackageSummarySupplier.PackageSummary> doInBackground(Void... voids) {

        List<PackageSummary> installedPackages = new ArrayList<>();

        Intent intent = new Intent(Intent.ACTION_MAIN, null);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

        List<ResolveInfo> resolveInfoList = context.getPackageManager().queryIntentActivities(intent, 0);

        for (ResolveInfo resolveInfo : resolveInfoList) {

            ActivityInfo activityInfo = resolveInfo.activityInfo;

            installedPackages.add(new PackageSummary(resolveInfo.activityInfo));
        }

        return installedPackages;
    }

And this is my Main activity OnCreate:

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    psList = SingletonPackageSummarySupplier.getInstance(context).getPackageSummaryListReadOnly();
    setContentView(R.layout.activity_main);

    recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

    recyclerViewLayoutManager = new LinearLayoutManager(context);

    recyclerView.setLayoutManager(recyclerViewLayoutManager);


    adapter = new AdapterApplist(context, psList);

    recyclerView.setAdapter(adapter);
}

And this is how the singleton is fetched:

static SingletonPackageSummarySupplier instance;
public static SingletonPackageSummarySupplier getInstance(Context context) {
    if (instance == null) {
        instance = new SingletonPackageSummarySupplier(context);
    } else{
        instance.updateInstance(context);
    }
    return instance;
}

P.S.: I think (but not sure) the singleton pattern is justified in order to diminish the changes of memory leaks.

P.S.2: I have read a couple questions about this, but none had an accepted / working solution.

itarill
  • 323
  • 1
  • 14

3 Answers3

0

Just add adapter.notifyDataSetChanged(); in your postexecute method of AsyncTask

AskNilesh
  • 67,701
  • 16
  • 123
  • 163
Varun Jain
  • 171
  • 2
  • 14
0

There are two steps: Update your data to AdapterApplist and notify it. Hence you should create a new method like:

public void setData(List<SingletonPackageSummarySupplier.PackageSummary> list)         
{ //reset your data ere
}

inside AdapterApplist class. Then, update your post:

@Override
    protected void onPostExecute(List<SingletonPackageSummarySupplier.PackageSummary> packageSummaries) {
        super.onPostExecute(packageSummaries);
        isQueryingInProgress = false;
        packageSummaryList = packageSummaries;
        adapter.setData(packageSummaryList);
        adapter.notifyDataSetChanged();
    }
Cao Minh Vu
  • 1,900
  • 1
  • 16
  • 21
  • This will cause that I need to pass on a RecyclerView.Adapter object to the Singleton. Also, the application list is an argument in the RecyclerView.Adapter's constructor. Either way, even if I can get it to work it does not really help clean code. Or am I not understanding it well? – itarill Mar 21 '18 at 10:11
  • It is a dirty way to make it work. A clean code is to use Observer design pattern in this case – Cao Minh Vu Mar 21 '18 at 10:20
  • My google skills seem inadequate to find a good guide for this pattern (apart from data binding and butterknife) . Can you maybe provide me a link for one? – itarill Mar 21 '18 at 10:32
  • You can find an example here: https://stackoverflow.com/questions/42983101/using-observable-in-android My advice is to implement a simple one using pure Java (not Android) to understand it first – Cao Minh Vu Mar 21 '18 at 10:37
0

Either I was unable to implement the observer pattern (note that the asynctask was in a singleton class separate from the recyclerview and activity), or it is just not working well in this case.

Anyway, I ended up fixing the issue with the relatively new lifecycle-aware components: MutableLiveData, LiveData and AndroidViewModel (instead of viewmodel, which does not get context as constructor parameter). It is simple and elegant.

The key part was this, in the activity:

    PackageSummarySupplier model = ViewModelProviders.of(this).get(PackageSummarySupplier.class);
    model.getPackageSummaryList().observe(this, packageSummaryList -> {
        adapter = new AdapterApplist(context, packageSummaryList);

        recyclerView.setAdapter(adapter);
    });
itarill
  • 323
  • 1
  • 14