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!
I guess the way you pass the data from MainActivity
to HomeFragment
is incorrect.
WHAT YOU EXPECT
MainActivity#GetBooksAsync
onPostExecute
has been calledHomeFragment
is ready to receive the broadcast message, then update UI MainActivity
to the fragmentWHAT IS HAPPENING HERE
MainActivity#GetBooksAsync
onPostExecute
has been calledMainActivity
. There is no receiver to receive this message!HomeFragment
is ready to receive the broadcast message, then update UI HOW SHALL YOU PASS THE DATA THEN?
There are several way.
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.
Build a singleton class to store the data. Your activity and fragment treats the singleton class as a common place for the data storage.
Use Intent and the extra property to pass the data IF the data size is small enough.
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:
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).
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.