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
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.
Related
I'm trying to populate a RecyclerView by following the nexts steps:
Download data from server and getting a SoapObject (yah, old server)
Transform the data to Flowable<List<MyItem>> (in Repository) in order to subscribe to it (in ViewModel) through LiveDataStreams.fromPublisher(flowableObj)
Set the resulted list into a MediatorLiveData object.
Observe the MediatorLiveData object in the Fragment's onViewCreated method.
So, when I click an on item from the list, it navigates (through Navigation Component) to a new Fragment, but, once I go back through the phone's back button, the list becomes empty and consequently the observer is notified and updates the list (shows nothing cause is empty).
I don't know why, the list gets empty and therefore the RecyclerView. Any help? -- code below:
Generic Fragment
public abstract class ListFragment<T> extends Fragment {
protected ListViewModel mViewModel;
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavController = Navigation.findNavController(getDataBindingObject().getRoot());
showItemsList();
setUpFilters();
}
protected void showItemsList() {
mViewModel.getList().observe(getViewLifecycleOwner(), listObserver);
mViewModel.getItemSelected().observe(getViewLifecycleOwner(), onListItemSelected());
}
protected final Observer<List<T>> listObserver = new Observer<List<T>>() {
#Override
public void onChanged(List<T> list) {
mViewModel.setListAdapter(list);
}
};
MyItem Fragment's code:
#Override
public View onCreateView(#NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mViewModel = new ViewModelProvider(this, new ViewModelFactory()).get(MyItemViewModel.class);
mDataBinding = ...
mDataBinding.setLifecycleOwner(this); //geViewLifecycleOwner()
mDataBinding.setViewModel(mViewModel);
return mDataBinding.getRoot();
}
#Override
public void onViewCreated(#NonNull #NotNull View view, #Nullable #org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavController = NavHostFragment.findNavController(this);
}
Generic ViewModel:
public abstract class ListViewModel<T, K> extends MyViewModel {
protected ListRepository<T> mRepository;
protected MediatorLiveData<List<T>> list;
protected MutableLiveData<K> mListAdapter;
public ListViewModel() {
super();
}
public LiveData<List<T>> getList() {
if (list == null) {
LiveData<List<T>> lD = LiveDataReactiveStreams.fromPublisher(mRepository.getList());
list = new MediatorLiveData<>();
list.addSource(lD, li -> {
this.list.postValue(li);
list.remove(lD); //removing this line does not work either
});
}
return list;
}
public LiveData<K> getListAdapter() {
if (mListAdapter == null)
mListAdapter = new MutableLiveData<>();
return mListAdapter;
}
public abstract void setListAdapter(List<T> list);
MyItemViewModel:
public class MyItemViewModel extends ListViewModel<MyItem, MyItemAdapter> {
protected MyItemRepository mHistoryRepository;
public MyItemViewModel(MyItemRepository repository) {
super();
mRepository = repository;
}
#Override
public void setListAdapter(List<MyItem> list) {
if (getListAdapter().getValue() == null) {
MyItemAdapter adapter = new MyItemAdapter(list);
adapter.setListener(onListItemSelectedListener);
mListAdapter.setValue(adapter);
} else
mListAdapter.getValue().updateList(list);
}
Generic Repository
public abstract class ListRepository<T> {
protected Flowable<List<T>> list;
protected abstract Flowable<List<T>> getItemsList(int orderByField);
public Flowable<List<T>> getList() {
if (list == null)
list = getItemsList();
return list;
}
MyItemRepository:
public class MyItemRepository extends ListRepository<MyItem> {
protected static volatile MyItemRepository instance;
protected final MyItemLocalDS mLocalDataSource;
protected final MyItemRemoteDS mRemoteDataSource;
public MyItemRepository(MyItemRemoteDS remoteDataSource,
MyItemLocalDS localDataSource) {
this.mRemoteDataSource = remoteDataSource;
this.mLocalDataSource = localDataSource;
}
public static MyItemRepository getInstance(MyRemoteDS remoteDataSource,
MyLocalDS localDataSource) {
if (instance == null)
instance = new MyItemRepository(remoteDataSource, localDataSource);
return instance;
}
#Override
protected Flowable<List<MyItem>> getItemsList() {
list = mRemoteDataSource.download(...)
.map(soapObject -> parseItemsList(soapObject))
.map(wsResult -> transformItemsList(wsResult));
return list.subscribeOn(Schedulers.io());
}
I have a dataset of restaurants and a recycler view where they are displayed. Depending on a few options, they should or not be visible: opening time, food type, etc.
Right now every time the activity with the recycler view is opened I run adapter.updateDataset() which internally goes through the whole dataset, creates a subset based on all the possible filters, and then does notifyDataSetChanged().
How can I make it so that I only need to run adapter.updateDataset() when a change actually occurs? Since these changes occur in a different context from the RecyclerView activity, I can't just call the function there. What alternative do I have, to improve performance?
You should probably use a list of LiveData objects either from room or a network resource and bind it your viewmodel. Then you will be observing the changes in your fragment/activity. When the change occurs, update the adapters data list and do not forget to use DiffUtil in order to update only changed items. A good example is in google sample codes on room database usage.
In your Room Dao query it should be like:
#Query("SELECT * FROM products")
LiveData<List<ProductEntity>> loadProducts();
Then in your viewmodel:
public class ProductListViewModel extends AndroidViewModel {
// MediatorLiveData can observe other LiveData objects and react on their emissions.
private final MediatorLiveData<List<ProductEntity>> observableProducts;
public ProductListViewModel(#NonNull Application application) {
super(application);
observableProducts = new MediatorLiveData<>();
// set by default null, until we get data from the database.
observableProducts.setValue(null);
LiveData<List<ProductEntity>> products = ((YourBaseApp) application).getRepository()
.loadProducts();
observableProducts.addSource(products, observableProducts::setValue);
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
#NonNull
private final Application mApplication;
public Factory(#NonNull Application application) {
mApplication = application;
}
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
//noinspection unchecked
return (T) new ProductListViewModel(mApplication);
}
}
public LiveData<List<ProductEntity>> getProductList() {
return observableProducts;
}
}
Then in your activity/fragment onCreate you may call such a sample function and start observing your data:
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
// Binding is of type ProductListLayoutBinding
// you need to declare it on tope of your fragment
binding = DataBindingUtil.inflate(inflater, R.layout.product_list_layout, container, false);
// your other stuff if needed..
productAdapter = new ProductAdapter(/*...Your parameters if any*/);
binding.yourRecylerViewId.setAdapter(productAdapter);
return binding.getRoot();
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//...
//...
// do your normal stuff above
ProductListViewModel.Factory factory = new ProductListViewModel.Factory(
YourBaseApp.getInstance());
final ProductListViewModel viewModel =
new ViewModelProvider(this, factory).get(ProductListViewModel.class);
subscribeUi(viewModel);
}
private void subscribeUi(ProductListViewModel viewModel) {
// Update the list when the data changes
viewModel.getProductList().observe(this, new Observer<List<ProductEntity>>() {
#Override
public void onChanged(#Nullable List<ProductEntity> myProducts) {
if (myProducts != null) {
if (myProducts.size() == 0) {
binding.setIsLoading(true);
} else {
binding.setIsLoading(false);
productAdapter.setProductList(myProducts);
}
} else {
binding.setIsLoading(true);
}
binding.executePendingBindings();
}
});
}
Finally on your adapter:
public void setProductList(final List<? extends Product> inProductList) {
if (productList == null) {
productList = inproductList;
notifyItemRangeInserted(0, productList.size());
} else {
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
#Override
public int getOldListSize() {
return productList.size();
}
#Override
public int getNewListSize() {
return inproductList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return productList.get(oldItemPosition).getId() == inproductList.get(newItemPosition).getId();
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
productList newProduct = inproductList.get(newItemPosition);
productList oldProduct = productList.get(oldItemPosition);
return newProduct.getId() == oldProduct.getId()
&& Objects.equals(newProduct.getDefinition(), oldProduct.getDefinition())
//... compare other properties
//...
;
}
});
productList = inproductList;
result.dispatchUpdatesTo(this);
}
}
Hope , this helps.
It is necessary to put the data in LiveData to send to the callback. In this method:
public void setData(List<Data> data) {
this.currentData.setValue((Data) data);
}
according to the documentation setValue is called by MutableLiveData, I replaced the LiveData in ViewModel with MutableLiveData, but anyway, when I open the required fragment, the application crashes
java.lang.ClassCastException: androidx.room.RoomTrackingLiveData cannot be cast to androidx.lifecycle.MutableLiveData
at avocado.droid.ptitsami.room.DataViewModel.<init>(DataViewModel.java:24)
at avocado.droid.ptitsami.room.DataViewModel$ModelFactory.create(DataViewModel.java:54)
at androidx.lifecycle.ViewModelProvider$FactoryWrapper.create(ViewModelProvider.java:268)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:179)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:147)
at avocado.droid.ptitsami.fragment.DataFragment.onCreateView(DataFragment.java:57)
How to fix it?
ViewModel
public class DataViewModel extends AndroidViewModel {
MutableLiveData<Data> currentData;
DataRepository repository;
public DataViewModel(#NonNull Application application, final int verseId) {
super(application);
int verseId1 = verseId;
repository = new DataRepository(application);
currentData = (MutableLiveData<Data>) repository.getById(verseId);
}
public LiveData<Data> getById() {
return currentData;
}
public void setData(List<Data> data) {
this.currentData.setValue((Data) data);
}
public static class ModelFactory extends ViewModelProvider.NewInstanceFactory {
#NonNull
private final Application application;
private final int dataId;
private final DataRepository repository;
public ModelFactory(#NonNull Application application, int id) {
super();
this.application = application;
this.dataId = id;
repository = new DataRepository(application);
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if (modelClass == DataViewModel.class) {
return (T) new DataViewModel(application, dataId);
}
return null;
}
}
Fragment
public class DataFragment extends Fragment {
private int dataId;
private static final String KEY_DATA_ID = "KEY_DATA_ID";
public TextView tvTitle;
public DataFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootViewRead = inflater.inflate(R.layout.fragment_data, container, false);
Toolbar toolbar = rootViewRead.findViewById(R.id.toolbar);
AppCompatActivity activity = (AppCompatActivity) getActivity();
if (activity != null) {
activity.setSupportActionBar(toolbar);
}
setHasOptionsMenu(true);
tvTitle = (TextView) rootViewRead.findViewById(R.id.text);
DataViewModel.ModelFactory factory = new DataViewModel.ModelFactory(
getActivity().getApplication(), getArguments().getInt(KEY_DATA_ID));
final DataViewModel model = ViewModelProviders.of(this, factory)
.get(DataViewModel.class);
model.getById().observe(this, new Observer<Data>() {
#Override
public void onChanged(Data data) {
model.setData((List<Data>) data);
}
});
return rootViewRead;
}
public static DataFragment forData(int dataId) {
DataFragment fragment = new DataFragment();
Bundle args = new Bundle();
args.putInt(KEY_DATA_ID, dataId);
fragment.setArguments(args);
return fragment;
}
repository
public class DataRepository {
private DatabaseCopier db;
DataRepository(Application application) {
db = DatabaseCopier.getInstance(application);
}
LiveData<Data> getById(int id) {
return db.getDatabase().dataDao().getById(id);
}
Try adding source to currentData and change currentData from MutableLivaData to MediatorLiveData.
LiveData<Data> data = repository.getById(verseId);
currentData.addSource(data, observer);
There are multiple things odd here..
In your ViewModel you have a getter and a setter for the LiveData:
public LiveData<Data> getById() {
return currentData;
}
public void setData(List<Data> data) {
this.currentData.setValue((Data) data);
}
And in your observer of the LiveData you call the setter of the LiveData??
model.getById().observe(this, new Observer<Data>() {
#Override
public void onChanged(Data data) {
model.setData((List<Data>) data);
}
});
That does not make sense! When the observe method is called, the model already has this data set! So you do not need to call setData. Without the main issue, this will create an endless loop!
Now to your main Issue:
androidx.room.RoomTrackingLiveData cannot be cast to androidx.lifecycle.MutableLiveData
Room data can only be loaded to LiveData. The reason is because Room always keeps a link to it and automatically updates it, once the content of the database changed! But therefore YOU cannot change the content of the LiveData!
So please explain why
It is necessary to put the data in LiveData to send to the callback.
You have to change:
//MutableLiveData<Data> currentData;
LiveData<Data> currentData;
and
model.getById().observe(this, new Observer<Data>() {
#Override
public void onChanged(Data data) {
//model.setData((List<Data>) data); <- this creates an endless loop
// do here what you want to do with the content of the data
// if you need to pass it to the viewmodel, do it, but do not call `setValue`
}
});
For specific usecases a MediatorLiveData might be reasonable, but for this you would have to explain in detail why above doesn't do the job for you.
I have a Fragment containing a RecyclerView for a list of items (groups) from a database. The user can navigate to a separate page to create a new group. In that action the group gets inserted into the database via a repository with asynchronous task. During that, the user is returned to the page holding the list, so the onResume method of the Fragment is called. When the insert record database operation is completed, the Repository posts the new list in the LiveData. The adapter for the group list is updated, but the view is not. I don't know why. I thought I was following the MVVM pattern properly.
Here is logger output, showing that the insert operation completes after onResume. The GroupListAdapter method to update the list is called, as expected. In this case "GroupListAdapter: SetGroups 2" meaning 2 groups, after having 1 before the user creates the 2nd.
HomeFragment onAttach
HomeFragment onCreate
HomeFragment onCreateView
GroupsFragment onAttach
GroupsFragment onCreate
GroupsFragment initData
GroupListViewModel created
GroupsFragment onCreateView
GroupsFragment: Do group list view
GroupsFragment list changed
GroupListAdapter: setGroups null
GroupListAdapter: setGroups inserted
GroupsFragment: Do group list view
GroupsFragment onResume
GroupsFragment: Do group list view
Repository: on group list change.
GroupsFragment list changed
GroupListAdapter: setGroups 1
GroupListAdapter: setGroups inserted
GroupsFragment: Do group list view
GroupsFragment list changed
GroupListAdapter: setGroups 0
GroupListAdapter dispatch updates
GroupListAdapter: setGroups 1 <<<<<<<<<< Initially 1 item in the group
GroupListAdapter dispatch updates
GroupsFragment: Do group list view
Finish create group activity. Result: Intent { (has extras) }
MainActivity onActivityResult for request 6, result: -1
Handle result of create group activity.
GroupListViewModel created
GroupListViewModel insert group
Created insert group task.
Async insert group.
Group inserted. <<<<<<<<<<<<<<<<<<<<<<< insertion completes before fragment's onResume
GroupsFragment onResume
GroupsFragment: Do group list view
Repository: on group list change.
GroupsFragment list changed
GroupListAdapter: setGroups 2 <<<<<<<<<<<<< The adapter knows there are 2 items in the list
GroupListAdapter dispatch updates
GroupsFragment: Do group list view >>>>>>>>>>>>>>>> Fragment should update, but does not.
Following are classes involves.
The Fragment:
public class GroupsFragment extends Fragment
{
private Context m_context = null;
private RecyclerView rv_groups;
private GroupListAdapter adapter = null;
private TextView tv_noGroups;
private GroupListViewModel m_groupViewModel;
public GroupsFragment ()
{} // Required empty public constructor
#Override
public void onAttach (Context context)
{
Logger.get().fine("GroupsFragment onAttach");
super.onAttach(context);
m_context = context;
}
#Override
public void onCreate (#Nullable Bundle savedInstanceState)
{
Logger.get().fine("GroupsFragment onCreate");
super.onCreate(savedInstanceState);
initData();
}
private void initData ()
{
Logger.get().fine("GroupsFragment initData");
m_groupViewModel = ViewModelProviders.of(this).get(GroupListViewModel.class);
if (m_groupViewModel.getAllGroups() == null)
Logger.get().severe("null group list live data in viewmodel");
m_groupViewModel.getAllGroups().observe(this, groups -> {
Logger.get().fine("GroupsFragment list changed");
adapter.setGroups(groups); // FIXME: Shouldn't this cause view update?
doListView(); // FIXME: Shouldn't need this?
});
}
#Override
public View onCreateView (#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
Logger.get().fine("GroupsFragment onCreateView");
View view = inflater.inflate (R.layout.fragment_groups, container, false);
adapter = new GroupListAdapter(m_context);
initListView(view);
FloatingActionButton fab_add;
fab_add = view.findViewById(R.id.fab_add);
fab_add.setOnClickListener (v -> {
// FIXME: getActivity may return null if fragment is associated with Context, not Activity
getActivity().startActivityForResult(new Intent(getContext(), CreateGroupActivity.class), Activities.CREATE_GROUP);
});
setHasOptionsMenu (true);
return view;
}
protected void initListView (View view)
{
rv_groups = view.findViewById(R.id.rv_groups);
rv_groups.setAdapter(adapter);
tv_noGroups = view.findViewById(R.id.tv_noGroups);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(m_context);
rv_groups.setLayoutManager(mLayoutManager);
rv_groups.setItemAnimator(new DefaultItemAnimator());
rv_groups.addItemDecoration(new DividerItemDecoration(m_context, DividerItemDecoration.VERTICAL));
doListView();
}
/** Indicate on the UI that there are no groups to display. */
private void showNoGroups ()
{
rv_groups.setVisibility(View.GONE);
tv_noGroups.setVisibility(View.VISIBLE);
tv_noGroups.setText(getResources().getString(R.string.string_noGroups));
}
private void doListView ()
{
Logger.get().fine("GroupsFragment: Do group list view");
if (m_groupViewModel == null || m_groupViewModel.countGroups() == 0)
{
showNoGroups();
}
else
{
rv_groups.setVisibility(View.VISIBLE);
tv_noGroups.setVisibility(View.GONE);
}
}
#Override
public void onResume ()
{
Logger.get().fine("GroupsFragment onResume");
super.onResume();
doListView();
}
}
The ViewModel:
public class GroupListViewModel extends AndroidViewModel
{
private final Repository m_repository;
private final MediatorLiveData<List<GroupEntity>> m_groups;
public GroupListViewModel (#NonNull Application application)
{
super(application);
m_groups = new MediatorLiveData<>();
// set null until we get data from the database.
m_groups.setValue(null);
m_repository = ((MyApp)application).getRepository();
LiveData<List<GroupEntity>> groups = m_repository.getAllGroups();
// observe the changes of the groups from the database and forward them
m_groups.addSource(groups, m_groups::setValue);
Logger.get().finer("GroupListViewModel created");
}
public int countGroups ()
{
if (m_groups.getValue() == null)
return 0;
return m_groups.getValue().size();
}
public LiveData<List<GroupEntity>> getAllGroups ()
{ return m_groups; }
public List<GroupEntity> searchGroups (String query)
{ return m_repository.searchGroups(query); }
public void insert (GroupEntity group)
{
Logger.get().finer("GroupListViewModel insert group");
m_repository.insertGroup(group);
}
public void update (GroupEntity group)
{
Logger.get().finer("GroupListViewModel update group");
m_repository.updateGroup(group);
}
}
The Adapter:
public class GroupListAdapter extends RecyclerView.Adapter<GroupListAdapter.GroupViewHolder>
{
private List<? extends Group> m_groupList;
private Context context;
public GroupListAdapter (Context context)
{
this.context = context;
}
class GroupViewHolder extends RecyclerView.ViewHolder
{
TextView name;
LinearLayout lay_group;
GroupViewHolder (View itemView)
{
super(itemView);
name = itemView.findViewById(R.id.tv_name);
lay_group = itemView.findViewById(R.id.lay_groups);
}
}
#Override
#NonNull
public GroupViewHolder onCreateViewHolder (#NonNull ViewGroup parent, int viewType)
{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_groups, parent, false);
return new GroupViewHolder(view);
}
#Override
public void onBindViewHolder (#NonNull final GroupViewHolder holder, int position)
{
final Group group = m_groupList.get(position);
holder.name.setText(group.getName());
// Set listener to show group details when group in list is clicked
holder.lay_group.setOnClickListener(v -> {
Intent intent = new Intent(context, GroupDetailActivity.class);
Logger.get().fine("Start group details activity for id " + group.getId());
intent.putExtra(GroupEntity.GROUP_ID_KEY, group.getId());
context.startActivity(intent);
});
}
public void setGroups (#Nullable final List<? extends Group> groups)
{
Logger.get().fine("GroupListAdapter: setGroups " + (groups == null ? "null" : groups.size()));
if (m_groupList == null)
{
m_groupList = groups;
notifyItemRangeInserted(0, groups == null ? 0 : groups.size());
Logger.get().fine("GroupListAdapter: setGroups inserted");
return;
}
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback()
{
#Override
public int getOldListSize ()
{ return m_groupList.size(); }
#Override
public int getNewListSize ()
{ return groups.size(); }
#Override
public boolean areItemsTheSame (int oldItemPosition, int newItemPosition)
{ return m_groupList.get(oldItemPosition).getId() == groups.get(newItemPosition).getId(); }
#Override
public boolean areContentsTheSame (int oldItemPosition, int newItemPosition)
{
Group newGroup = groups.get(newItemPosition);
Group oldGroup = m_groupList.get(oldItemPosition);
return newGroup.getId() == oldGroup.getId()
&& (CommonUtils.equalStrings(newGroup.getName(), oldGroup.getName()));
}
});
m_groupList = groups;
Logger.get().finer("GroupListAdapter dispatch updates");
result.dispatchUpdatesTo(this);
}
#Override
public int getItemCount()
{
// Must allow for groups not completed loading yet
if (m_groupList == null)
return 0;
return m_groupList.size();
}
#Override
public long getItemId (int position)
{ return m_groupList.get(position).getId(); } // Note online BasicSample example does not check for null list here.
}
Repository:
public class Repository
{
private static Repository sInstance;
private final Database m_database;
private GroupDAO m_groupDAO;
private MediatorLiveData<List<GroupEntity>> m_observableGroups;
//private LiveData<List<GroupEntity>> m_groups;
public static Repository getInstance (final Database database)
{
if (sInstance == null)
{
synchronized (Repository.class)
{
if (sInstance == null)
sInstance = new Repository(database);
}
}
return sInstance;
}
private Repository (final Database database)
{
m_database = database;
load();
}
public Repository (Application application)
{
this(Database.getDatabase(application));
}
/**
* Get all data access objects.
*/
private void getDAO ()
{
m_groupDAO = m_database.groupDAO();
}
/**
* Get objects from the database, to store in this repository.
*/
private void load ()
{
getDAO();
Logger.get().info("Load objects to repository");
m_observableGroups = new MediatorLiveData<>();
m_observableGroups.addSource(m_database.groupDAO().loadAllSync(),
new Observer<List<GroupEntity>>()
{
#Override
public void onChanged (List<GroupEntity> groupEntities)
{
Logger.get().info("Repository: on group list change.");
if (m_database.getDatabaseCreated().getValue() != null)
m_observableGroups.postValue(groupEntities);
}
}
);
}
public LiveData<List<GroupEntity>> getAllGroups ()
{ return m_observableGroups; }
/*private LiveData<GroupEntity> loadGroup (final int id)
{ return m_database.groupDAO().loadById(id); }*/
// TODO which getGroup? return LiveData<GroupEntity> ?
/*public GroupEntity getGroup (int id)
{
Logger.get().finer("Repository: getGroup.");
for (GroupEntity group : getAllGroups().getValue())
{
if (group.getId() == id)
return group;
}
return null;
}*/
public GroupEntity getGroup (int id)
{
Logger.get().finer("Repository: getGroup.");
return m_database.groupDAO().loadById(id);
}
public LiveData<GroupEntity> getGroupSync (int id)
{
Logger.get().finer("Repository: getGroup.");
return m_database.groupDAO().loadByIdSync(id);
}
public void insertGroup (GroupEntity group)
{ new insertGroupTask(m_groupDAO).execute(group); }
private static class insertGroupTask extends AsyncTask<GroupEntity, Void, Void>
{
private GroupDAO mAsyncTaskDao;
insertGroupTask(GroupDAO dao) {
Logger.get().info("Created insert group task.");
mAsyncTaskDao = dao;
}
#Override
protected Void doInBackground (final GroupEntity... params)
{
Logger.get().info("Async insert group.");
mAsyncTaskDao.insert(params[0]);
Logger.get().fine("Group inserted.");
return null;
}
}
}
Group:
public interface Group
{
...
}
Group Entity
#Entity(tableName = "groups")
public class GroupEntity implements Group
{
...
}
No mistake in the java. A GUI newbie mistake in the layout definition meant the groups would be displayed one per page. Changed TextView layout_height to "wrap_content".
I've got a custom list view with my own adapter. I can add objects to this list within the same activity, but what I want to do is add objects from another activity. In this other activity there are two Edit Text boxes. One is responsible for Main text, second for Description, and two radio buttons that are determining image. At the bottom is Add button, that should add entered data to the list view.
I tried to do this with Parcelable, but I don't know exactly how to implement it. Below is my ListView class:
public class Activity_4 extends AppCompatActivity {
private ListView listView;
private myAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_4);
listView = (ListView)findViewById(R.id.listView4);
ArrayList<Object> objectList = new ArrayList<>();
objectList.add(new Object(R.drawable.row_img, "After Earth" , "2013"));
objectList.add(new Object(R.drawable.row_img, "After Earth" , "2013"));
objectList.add(new Object(R.drawable.row_img, "After Earth" , "2013"));
objectList.add(new Object(R.drawable.row_img, "After Earth" , "2013"));
objectList.add(new Object(R.drawable.row_img, "After Earth" , "2013"));
objectList.add(new Object(R.drawable.row_img, "After Earth" , "2013"));
mAdapter = new myAdapter(this,objectList);
listView.setAdapter(mAdapter);
}
public class Object implements Parcelable {
int imageID;
String mainText;
String description;
public Object (int imageID, String mainText, String description) {
this.imageID = imageID;
this.mainText = mainText;
this.description = description;
}
public Object() {
}
public int getImageID() {
return imageID;
}
public void setImageID(int imageID) {
this.imageID = imageID;
}
public String getMainText () {
return mainText;
}
public void setMainText() {
this.mainText = mainText;
}
public String getDescription() {
return description;
}
public void setDescription() {
this.description = description;
}
public Object(Parcel in) {
this.imageID = in.readInt();
this.mainText = in.readString();
this.description = in.readString();
}
public final Parcelable.Creator<Object> CREATOR = new Parcelable.Creator<Object>() {
#Override
public Object createFromParcel(Parcel in) {
return new Object(in);
}
#Override
public Object[] newArray(int size) {
return new Object[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(imageID);
dest.writeString(mainText);
dest.writeString(description);
}
}
public class myAdapter extends ArrayAdapter<Object> {
private Context mContext;
private List<Object> objectList = new ArrayList<>();
public myAdapter(#NonNull Context context, ArrayList<Object> list) {
super(context,0,list);
mContext = context;
objectList = list;
}
#NonNull
#Override
public View getView (int position, #Nullable View convertView, #NonNull ViewGroup parent) {
View listItem = convertView;
if (listItem == null)
listItem = LayoutInflater.from(mContext).inflate(R.layout.list_row, parent, false);
Object currentObject = objectList.get(position);
ImageView image = (ImageView) listItem.findViewById(R.id.row_image);
image.setImageResource(currentObject.getImageID());
TextView mainTxt = (TextView) listItem.findViewById(R.id.row_tv1);
mainTxt.setText(currentObject.getMainText());
TextView description = (TextView) listItem.findViewById(R.id.row_tv2);
description.setText(currentObject.getDescription());
return listItem;
}
}
Can anyone help with this problem?
Thanks
You can other Activity using startActivityForResult() and pass data in bundle as result on click of add button from other Activity, so current activity will add the data to it's list.
Calling Other Activity to add
startActivityForResult(intent,reqCode);
On Result
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data != null && requestCode == reqCode) {
Object object = new Object();
object.setImageID(data.getIntExtra("image_id"));
object.setMainText(data.getStringExtra("main_text");
object.setDescription(data.getStringExtra("desc");
objectlist.add(object);
mAdapter.notifyDataSetChanged();
}
}
Other Activity
public void onAddClick(){
Intent intent = new Intent();
intent.putIntExtra("image_id",*<value>*);
intent.putStringExtra("main_text",*<value>*);
intent.putStringExtra("desc",*<value>*);
setResult(intent);
finish();
}
A good starting point might be to start your EditText activity using startActivityForResult and pass the updates to the list to your adapter through the result, after you added everything you wanted by calling setResult with your new data. For further reference see Getting a Result from an Activity. Good luck!
When opening 2nd Activity from 1st Activity
do
Intent mIntent=new Intent(this,YourSecondActivity.class);
startActivityForResult(mIntent,SOME_STATIC_INT)
In another activity
on click of AddButton
Intent mIntent=new Intent(this,YourFirstActivity.class);
mIntent.putExtra("DATA",Object)
setResult(SOME_STATIC_INT,mIntent)
now in 1st Activity
Override onActivity Result
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data != null && requestCode == SOME_STATIC_INT) {
Object object = data.getParcelableExtra("DATA")
objectlist.add(object);
mAdapter.notifyDataSetChanged();
}
}