Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
156 views
in Technique[技术] by (71.8m points)

android - Livedata doesn't update data in fragment correctly

I have tablayout and 2 fragments in separate tabs. Fragment A have an overridden method that returns data when Activity (started from Fragment A) return data on it's destroy:

public class Fragment A extends Fragment {
    ...
    @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if(resultCode != RESULT_CANCELED) {
            assert data != null;
            String accountTransaction = data.getStringExtra("Account");
            String categoryTransaction = data.getStringExtra("Category");
            Double getDouble = data.getDoubleExtra("Value", 0);
            
            TransactionNewItem item = new TransactionNewItem(String.valueOf(getDouble),accountTransaction,categoryTransaction);
            model.setSelected(item);
  
        }
           super.onActivityResult(requestCode, resultCode, data);
    }
    }

In this same method I use a call to ViewModel that should observe TransactionNewItem object :

public class TransactionViewModel extends ViewModel {
    private final MutableLiveData<TransactionNewItem> selected = new MutableLiveData<>();
    public void setSelected (TransactionNewItem item){
        selected.setValue(item);
    }

    public LiveData<TransactionNewItem> getSelected() {
        return selected;
    }
}

After data that returns from Activity, with new values it creates a new POJO and sends data stored in this POJO to Fragment B, where based on data from Fragment A new item for RecyclerView will be created

public class Fragment B extends Fragment {
    ...
    @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            initObserve();
            initRecView();
           super.onViewCreated(view, savedInstanceState);
        }
        //init RecyclerView
        private void initRecView(){
            binding.transactionView.setLayoutManager(new LinearLayoutManager(requireContext()));
            adapter = new TransactionRecViewAdapter(listContentArr);
            adapter.setListContent(listContentArr);
            binding.transactionView.setAdapter(adapter);
        }
        
        //observe data from Fragment A and create object based on it
        private void initObserve(){
            model = new ViewModelProvider(requireActivity()).get(TransactionViewModel.class);
            model.getSelected().observe(getViewLifecycleOwner(), item -> {
                TransactionItem newAccountItem = new TransactionItem() ;
                newAccountItem.setTransactionValue(item.getTransactionValue());
                newAccountItem.setTransactionCategory(item.getTransactionCategory());
                newAccountItem.setTransactionAccount(item.getTransactionAccount());
                listContentArr.add(0,newAccountItem);
                adapter.notifyDataSetChanged();
            });
            }
}

However, it will add only 1 item into RecyclerView and will replace it with when Activity returns new data. This happens if the user didn’t switch to Fragment B at least one time, because onViewCreated isn't called till the user switches to Fragment B.

How to make ViewModel observe data from Fragment A, and create new TransActionItem in Fragment B Recyclerview every time the Activity returns new data if the user never switched to Fragment B before?

Thanks in advance

question from:https://stackoverflow.com/questions/65871386/livedata-doesnt-update-data-in-fragment-correctly

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

EDIT: I managed to do what I want in a next way:
STEP 1. I change ViewModel from POJO to Arraylist with POJO - ArrayList:

public class TransactionViewModel extends ViewModel {

        private final MutableLiveData<ArrayList<TransactionItem>> selected = new MutableLiveData<>();
  
        public void setSelected (ArrayList<TransactionItem> arrayList){
            selected.setValue(arrayList);
        }
    
        public LiveData<ArrayList<TransactionItem>> getSelected() {
            return selected;
        }
    }

STEP 2. In Fragment A I added ArrayList with the same POJO type, in onActivityResult. I changed the code now object will be created and added after Activity will return a result, not in Fragment B:

public class Fragment A extends Fragment {
    ArrayList<TransactionItem> listTransactions = new ArrayList<>();
    …
@Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if(resultCode != RESULT_CANCELED) {
            ... 
        //Create TransactionItem and use setSelected method from ViewModel
            TransactionItem item = new TransactionItem(accountTransaction,
                    String.valueOf(getDouble),categoryAccount),transactionID);
            listTransactions.add(0,item);
            model.setSelected(listTransactions);            
        }
           super.onActivityResult(requestCode, resultCode, data);
    }
    }

Must notice that I added transactionID into the TransactionItem constructor, and that's why we need it.
STEP 3 I created next TransactionDiffUtilCallback class that extends DiffUtil.Callback :

public class TransactionDiffUtilCallback extends `DiffUtil.Callback` {
   
     public TransactionDiffUtilCallback(ArrayList<TransactionItem> oldList, ArrayList<TransactionItem> newList) {
            this.oldList = oldList;
            this.newList = newList;
        }
    
        ArrayList<TransactionItem> newList;
    
        @Override
        public int getOldListSize() {
            return oldList.size();
        }
    
        @Override
        public int getNewListSize() {
            return newList.size();
        }
    
        @Override
        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
            return oldList.get(oldItemPosition).getItemIID() == newList.get(newItemPosition).getItemIID();
        }
    
        @Override
        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
            return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
        }
    
        @Nullable
        @Override
        public Object getChangePayload(int oldItemPosition, int newItemPosition) {
           
            return super.getChangePayload(oldItemPosition, newItemPosition);
        }
    }

I used getItemIID() from POJO to notify that the new item in ArrayList is different.

STEP 4 In recyclerview adapter I created updateItemList(list):

public void updateItemList(ArrayList<TransactionItem> items){
    final TransactionDiffUtilCallback diffCallback = new TransactionDiffUtilCallback(this.pad_list, items);
    final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);

    this.pad_list.clear();
    this.pad_list.addAll(items);
    diffResult.dispatchUpdatesTo(this);
}

So this method uses DiffUtil.CallBack to compare items in ArrayList from Fragment A and ArrayList in Fragment B, then notify adapter that ArrayList from Fragment A is different, and this data should be put in ArrayList in Fragment B, and view should be updated.

STEP 5 In Fragment B OnViewCreated() code was rewritten to observe Arraylist forever :

 @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    initRecView();
    model = new ViewModelProvider(requireActivity()).get(TransactionViewModel.class);
    Observer<ArrayList<TransactionItem>> observer = (Observer<ArrayList<TransactionItem>>) this::initObserve;
    model.getSelected().observeForever(observer);
    super.onViewCreated(view, savedInstanceState);
}

And initObserve() now have next code:

private void initObserve(ArrayList<TransactionItem> list){
    adapter.updateItemList(list);
}

For now, this solution is working, the user doesn’t need to switch to Fragment B to keep transaction recording. I will resume test this solution.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...