简体   繁体   中英

Passing data from Activity to Fragment using AsyncTask - Android

I'm trying to pass an ArrayList from an AsyncTask in the MainActivity to a fragment, but I'm getting a NullPointerException for invoking CategoryAdapter.getItemCount() even if I'm passing the array after the BroadCastReceiver Invoke.

What Am I doing wrong?

MainActivity

 class GetBooksAsync extends AsyncTask<Void, Void, Void> {
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getApplicationContext());
    @Override
    protected Void doInBackground(Void... voids) {
        for (ECategories category : ECategories.values()) {
            try {
                categories.add(new Category(category.toString(), apiClient.getBooks(category)));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        Intent intent = new Intent("com.android.mainapp");
        intent.putExtra("categories", categories);
        manager.sendBroadcast(intent);
        replaceFragment(new HomeFragment());
    }
}

HomeFragment

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    initBroadCastReceiver();
    categoryAdapter = new CategoryAdapter(categories,getContext());
    View view = inflater.inflate(R.layout.fragment_home, container, false);
    recyclerView = view.findViewById(R.id.parent_rv);
    recyclerView.setAdapter(categoryAdapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
    categoryAdapter.notifyDataSetChanged();
    return view;
}

private void initBroadCastReceiver() {
    manager = LocalBroadcastManager.getInstance(getContext());
    MyBroadCastReceiver receiver = new MyBroadCastReceiver();
    IntentFilter filter = new IntentFilter();
    filter.addAction("com.android.mainapp");
    manager.registerReceiver(receiver,filter);
}

class MyBroadCastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        //get the categories from the intent
        categories = new ArrayList<Category>();
        categories = (ArrayList<Category>) intent.getSerializableExtra("categories");
    }
}

i've also tried attaching the recyclerView from the OnReceive Method, but it's not getting attached. Thank you in advance!

  • Is Category serialized?
  • You can use BroadcastReceiver as an internal class, and then update the data of Adpater when it receives the data, because the code runs very fast, and it is not necessary to register for monitoring, and it will be processed immediately.

I guess the way you pass the data from MainActivity to HomeFragment is incorrect.

WHAT YOU EXPECT

  1. Call MainActivity#GetBooksAsync
  2. Wait till onPostExecute has been called
  3. HomeFragment is ready to receive the broadcast message, then update UI
  4. Broadcast the message from MainActivity to the fragment

WHAT IS HAPPENING HERE

  1. Call MainActivity#GetBooksAsync
  2. Wait till onPostExecute has been called
  3. Broadcast the message from MainActivity . There is no receiver to receive this message!
  4. HomeFragment is ready to receive the broadcast message, then update UI

HOW SHALL YOU PASS THE DATA THEN?

There are several way.

  1. Broadcast data between the UI component like the things you did. But you will need to beaware the life cycle of the components . That is, when you broadcast the data, the receiver must already init and the UI component is in active.

  2. Build a singleton class to store the data. Your activity and fragment treats the singleton class as a common place for the data storage.

  3. Use Intent and the extra property to pass the data IF the data size is small enough.

  4. Use LiveData. I believe it is the most modern way recommended by the community. Though I am not sure how its work.


To verify the fact that it is an life cycle issue,

you can try to add a delay before you sending the broadcast message.

class GetBooksAsync extends AsyncTask<Void, Void, Void> {
...
    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        Intent intent = new Intent("com.android.mainapp");
        intent.putExtra("categories", categories);
        
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                manager.sendBroadcast(intent);
            }
        };

        Timer timer = new Timer();
        timer.schedule(task, 5 * 1000); // Delay the broadcast after 5 seconds

        
        replaceFragment(new HomeFragment());
    }



Your Adapter should be written like this.

    class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.VHolder>{
        
        private ArrayList<Category> list = new ArrayList<Category>();

        public void setList(ArrayList<Category> list) {
            this.list = list;
            notifyDataSetChanged();
        }

        public CategoryAdapter(Context context) {
            // Do not pass a list in the constructor, because the list may be empty
        }
         
        class VHolder extends RecyclerView.ViewHolder {

            public VHolder(@NonNull View itemView) {
                super(itemView);
            }
        }
        
        ......
}

Your fragment should have a global Adapter for BroadcastReceiver to update data

public class Test extends Fragment {

    // Create a global Adapter for BroadcastReceiver to call and update data
    private CategoryAdapter adapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        adapter = new CategoryAdapter(getContext());
        initBroadCastReceiver();
        View view = inflater.inflate(R.layout.fragment_home, container, false);
        recyclerView = view.findViewById(R.id.parent_rv);
        recyclerView.setAdapter(categoryAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        return view;
    }

    private void initBroadCastReceiver() {
        manager = LocalBroadcastManager.getInstance(getContext());
        MyBroadCastReceiver receiver = new MyBroadCastReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.android.mainapp");
        manager.registerReceiver(receiver,filter);
    }

    class MyBroadCastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            //get the categories from the intent
            ArrayList<Category> categories = (ArrayList<Category>) intent.getSerializableExtra("categories");
            adapter.setList(categories);
        }
    }
}

I think there are several problems with your code:

  1. Your task is running in a different thread than the UIThread (which schedules the task and processes the result). That means it most probably runs on a different processor/core. Processed values (such as your collection) are cached in a processor and somewhen after execution the data is written back to RAM. But that might happen after the onPostExecute method is called, which takes the collection to another processor cache as well. But when this is done before the collection is returned to the RAM from the task, it's still empty. That's called a race condition .

Now there are several ways to solve that. The simplest one is to use Collections.synchronizedList(categories) This prevents the processor from caching list values and always return it to the RAM (or using L3 cache which is shared between all processors/cores).

  1. I'm not sure what exactly you pass to the collection. Intents (and it's data) need to be serializable and what you add to your collection is probably not serializable.

Then I would use the AsyncTask parameters:

class GetBooksAsync extends AsyncTask<ECategories, Void, Collection<Category>> {
        LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getApplicationContext());
    @Override
    protected Void doInBackground(ECategories... eCategories) {
        Collection<Category> categories = [whatever you want to use];
        for (ECategories category : eCategories) {
            try {
                categories.add(new Category(category.toString(), apiClient.getBooks(category)));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return categories;
    }
    @Override
    protected void onPostExecute(Collection<Category> categories) {
        super.onPostExecute(categories);
        Intent intent = new Intent("com.android.mainapp");
        intent.putExtra("categories", categories);
        manager.sendBroadcast(intent);
        replaceFragment(new HomeFragment());
    }
}

And note that AsyncTask and LocalBroadcastManager are deprecated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM