diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryFragment.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryFragment.java index f20ae10..63c4be4 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryFragment.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryFragment.java @@ -7,6 +7,8 @@ import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavDirections; import androidx.navigation.Navigation; import androidx.recyclerview.widget.DividerItemDecoration; @@ -18,65 +20,52 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.data.MonsterRepository; +import com.majinnaibu.monstercards.databinding.FragmentLibraryBinding; import com.majinnaibu.monstercards.models.Monster; import com.majinnaibu.monstercards.ui.shared.MCFragment; import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback; import com.majinnaibu.monstercards.utils.Logger; -import java.util.UUID; +import java.util.List; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.observers.DisposableCompletableObserver; import io.reactivex.rxjava3.schedulers.Schedulers; public class LibraryFragment extends MCFragment { + private LibraryViewModel mViewModel; + private ViewHolder mHolder; + private LibraryRecyclerViewAdapter mAdapter; - public View onCreateView(@NonNull LayoutInflater inflater, - ViewGroup container, Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.fragment_library, container, false); - - FloatingActionButton fab = root.findViewById(R.id.fab); - assert fab != null; - setupAddMonsterButton(fab); - - final RecyclerView recyclerView = root.findViewById(R.id.monster_list); - assert recyclerView != null; - setupRecyclerView(recyclerView); - - return root; + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mViewModel = new ViewModelProvider(this).get(LibraryViewModel.class); + FragmentLibraryBinding binding = FragmentLibraryBinding.inflate(inflater, container, false); + mHolder = new ViewHolder(binding); + // TODO: set the title with setTitle(...) + setupAddMonsterButton(mHolder.addButton); + setupMonsterList(mHolder.list); + return binding.getRoot(); } - private void setupRecyclerView(@NonNull RecyclerView recyclerView) { + private void setupMonsterList(@NonNull RecyclerView recyclerView) { Context context = requireContext(); - MonsterRepository repository = this.getMonsterRepository(); - - LibraryRecyclerViewAdapter adapter = new LibraryRecyclerViewAdapter( - context, - repository.getMonsters(), - (monster) -> navigateToMonsterDetail(monster.id), - (monster) -> repository - .deleteMonster(monster) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableCompletableObserver() { - @Override - public void onComplete() { - } - - @Override - public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) { - Logger.logError(e); - } - })); - recyclerView.setAdapter(adapter); - LinearLayoutManager layoutManager = new LinearLayoutManager(context); recyclerView.setLayoutManager(layoutManager); + LiveData> monsterData = mViewModel.getMonsters(); + mAdapter = new LibraryRecyclerViewAdapter(this::navigateToMonsterDetail); + if (monsterData != null) { + monsterData.observe(getViewLifecycleOwner(), monsters -> mAdapter.submitList(monsters)); + } + recyclerView.setAdapter(mAdapter); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); recyclerView.addItemDecoration(dividerItemDecoration); - ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(requireContext(), (position, direction) -> adapter.deleteItem(position), null)); + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback( + requireContext(), + (position, direction) -> mViewModel.removeMonster(position), + null)); itemTouchHelper.attachToRecyclerView(recyclerView); } @@ -98,7 +87,7 @@ public class LibraryFragment extends MCFragment { view, getString(R.string.snackbar_monster_created, monster.name), Snackbar.LENGTH_LONG) - .setAction("Action", (_view) -> navigateToMonsterDetail(monster.id)) + .setAction("Action", (_view) -> navigateToMonsterDetail(monster)) .show(); } @@ -114,10 +103,22 @@ public class LibraryFragment extends MCFragment { }); } - protected void navigateToMonsterDetail(UUID monsterId) { - NavDirections action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monsterId.toString()); - View view = getView(); - assert view != null; - Navigation.findNavController(view).navigate(action); + protected void navigateToMonsterDetail(Monster monster) { + if (monster != null) { + NavDirections action = (NavDirections) LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monster.id.toString()); + Navigation.findNavController(requireView()).navigate(action); + } else { + Logger.logError("Can't navigate to MonsterDetail without a monster."); + } + } + + private static class ViewHolder { + final FloatingActionButton addButton; + final RecyclerView list; + + public ViewHolder(FragmentLibraryBinding binding) { + addButton = binding.fab; + list = binding.monsterList; + } } } diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryRecyclerViewAdapter.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryRecyclerViewAdapter.java index 82a80da..f9382e2 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryRecyclerViewAdapter.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryRecyclerViewAdapter.java @@ -1,52 +1,35 @@ package com.majinnaibu.monstercards.ui.library; -import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.models.Monster; -import java.util.ArrayList; -import java.util.List; - -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Flowable; -import io.reactivex.rxjava3.disposables.Disposable; -import io.reactivex.rxjava3.schedulers.Schedulers; - -public class LibraryRecyclerViewAdapter extends RecyclerView.Adapter { - private final Context mContext; - private final ItemCallback mOnDelete; - private final ItemCallback mOnClick; - private final Flowable> mItemsObservable; - private final View.OnClickListener mOnClickListener = new View.OnClickListener() { +public class LibraryRecyclerViewAdapter extends ListAdapter { + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { @Override - public void onClick(@NonNull View view) { - Monster monster = (Monster) view.getTag(); - if (mOnClick != null) { - mOnClick.onItemCallback(monster); - } + public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) { + return Monster.areItemsTheSame(oldItem, newItem); + } + + @Override + public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) { + return Monster.areContentsTheSame(oldItem, newItem); } }; - private List mValues; - private Disposable mDisposable; + private final ItemCallback mOnClick; - public LibraryRecyclerViewAdapter(Context context, - Flowable> itemsObservable, - ItemCallback onClick, - ItemCallback onDelete) { - mItemsObservable = itemsObservable; - mValues = new ArrayList<>(); - mContext = context; - mOnDelete = onDelete; + public LibraryRecyclerViewAdapter(ItemCallback onClick) { + super(DIFF_CALLBACK); mOnClick = onClick; - mDisposable = null; } @Override @@ -59,45 +42,15 @@ public class LibraryRecyclerViewAdapter extends RecyclerView.Adapter { - mValues = monsters; - notifyDataSetChanged(); - }); - } - - @Override - public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { - super.onDetachedFromRecyclerView(recyclerView); - mDisposable.dispose(); - } - - public void deleteItem(int position) { - if (mOnDelete != null) { - Monster monster = mValues.get(position); - mOnDelete.onItemCallback(monster); - } + holder.itemView.setOnClickListener(v -> { + if (mOnClick != null) { + mOnClick.onItemCallback(holder.item); + } + }); } public interface ItemCallback { @@ -105,11 +58,12 @@ public class LibraryRecyclerViewAdapter extends RecyclerView.Adapter mText; +import java.util.ArrayList; +import java.util.List; - public LibraryViewModel() { - mText = new MutableLiveData<>(); - mText.setValue("This is library fragment"); +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.observers.DisposableCompletableObserver; +import io.reactivex.rxjava3.schedulers.Schedulers; +import io.reactivex.rxjava3.subscribers.DisposableSubscriber; + +public class LibraryViewModel extends AndroidViewModel { + private final AppDatabase mDB; + private final MutableLiveData> mMonsters; + + public LibraryViewModel(Application application) { + super(application); + mDB = AppDatabase.getInstance(application); + mMonsters = new MutableLiveData<>(new ArrayList<>()); + mDB.monsterDAO() + .getAll() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe(new DisposableSubscriber>() { + @Override + public void onNext(List monsters) { + mMonsters.setValue(monsters); + } + + @Override + public void onError(Throwable t) { + + } + + @Override + public void onComplete() { + + } + }); } - public LiveData getText() { - return mText; + + public LiveData> getMonsters() { + return mMonsters; + } + + public void removeMonster(int position) { + Monster monster = mMonsters.getValue().get(position); + mDB.monsterDAO() + .delete(monster) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe(new DisposableCompletableObserver() { + @Override + public void onComplete() { + + } + + @Override + public void onError(@NonNull Throwable e) { + + } + }); } }