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!
CodePudding user response:
- 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.
CodePudding user response:
I guess the way you pass the data from MainActivity
to HomeFragment
is incorrect.
WHAT YOU EXPECT
- Call
MainActivity#GetBooksAsync
- Wait till
onPostExecute
has been called HomeFragment
is ready to receive the broadcast message, then update UI- Broadcast the message from
MainActivity
to the fragment
WHAT IS HAPPENING HERE
- Call
MainActivity#GetBooksAsync
- Wait till
onPostExecute
has been called - Broadcast the message from
MainActivity
. 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());
}
CodePudding user response:
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);
}
}
}