I want to update a textview of the recycler view with a live data.
how can I do this? I am a beginner please help
this is my viewModel class
class ViewModelClass : ViewModel() {
var rating = MutableLiveData<String>("NA")
}
I have a textView in a recycler view which I want to update after taking the text from the edit text but I could not do this because I can not get the reference of that textview in the MainActivity.
btnSave.setOnClickListener {
val rating : String = etRating.text.toString()
viewModel.rating.value = rating
}
Follow these steps:
Create an adapter to configure your recyclerview. Make sure to
implement de DiffUtils correctly.
class MyAdapter: ListAdapter<String, MyAdapter.MyAdapterViewHolder>(DIFF_CALLBACK) {
class MyAdapterViewHolder(
private val binding: ItemSimplePosterBinding
): RecyclerView.ViewHolder(binding.root) {
// implementation here
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
// need a unique identifier to have sure they are the same item. could be a comparison of ids. In this case, that is just a list of strings just compare like this below
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
// compare the objects
return oldItem == newItem
}
}
}
}
On your ViewModel, create a function that will add the new rating to your list:
class MyViewModel: ViewModel() {
private val _rating = MutableLiveData<List<String>>(emptyList())
val rating: LiveData<List<String>>
get() = _rating
// implement a function that adds a new item on the rating list
fun addRating(newRate: String) {
val newList = mutableListOf<String>()
_rating.value?.let { newList.addAll(it) }
newList.add(newRate)
_rating.value = newList
}
}
See that I didn't expose the mutableLiveData, always pay attention to following this best practice to let private the mutableLiveData and let public just the immutable live data.
Then, the last step is to observe the live data changes. So, when the list change on viewmodel, you will receive the updated list on the observer just submit the list for adapter:
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// set the adapter to your recycler view
// observe changes of your live data
viewModel.rating.observe(viewLifecycleOwner) { ratings ->
adapter.submitList(ratings)
}
}
}
Related
I'm starting using Kotlin (i'm a web dev) to maintain the mobile app of my current job. To practice my learning, I'm creating a basic app which is displaying a list of France departments (using a REST Api), and I need to allow the user to click on a list item to get more info on the selected item.
I'm trying to build this with databinding, Koin as dependency injection, and Room as db layer.
My issue is that I created a RecyclerView custom Adapter, and used the databinding to give it the datas. But now I want to implement the onClick behaviour, which should launch another activity to display item details. My problem is: I don't know how to do this in a clean way.
I was thinking about creating a viewModel to link to my Adapter, but can't really find how to do it well. And even if I did, how to start another activity in a viewModel ? (don't have access to the context and startActivity function). So I finally dropped that solution that doesn't seems to fit.
So I'm currently thinking of passing directly from my adapter the onClick function, but can't find a way to bind this function in my xml file. Here is my files:
MainActivity:
class MainActivity : AppCompatActivity() {
private val mViewModel: DepartmentsViewModel by viewModel()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.toolbar.title = "Liste des départements"
val adapter = DepartmentListAdaptater()
binding.recyclerview.adapter = adapter
binding.recyclerview.layoutManager = LinearLayoutManager(this)
mViewModel.allDepartments.observe(this, Observer { data -> adapter.submitList(data) })
}
}
RecyclerView.Adapter:
class DepartmentListAdaptater : RecyclerView.Adapter<DepartmentListAdaptater.ViewHolder>() {
private var dataSet: List<Department>? = null
inner class ViewHolder(private val binding: DepartmentListRowBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(department: Department?) {
binding.department = department
}
}
fun submitList(list: List<Department>) {
dataSet = list
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = DepartmentListRowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int = dataSet?.size ?: 0
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(dataSet?.get(position))
}
}
The XML View:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="department" type="com.navalex.francemap.data.entity.Department" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="72dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="#drawable/list_item_bg"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:clickable="true"
tools:ignore="UselessParent">
<TextView
android:id="#+id/textView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingRight="16dp"
android:text="#{department.nom}"
android:paddingLeft="16dp"/>
</LinearLayout>
</RelativeLayout>
</layout>
First I want to say that it's really impressive that you are a web developer and you already have a lot of knowledge about things like dependency injection and keep the state of the view on ViewModel, congrats. Now, let's talk about your problem... I'll start with some suggestions that will improve the code clarity and performance.
For the Adapter implementation, always prefer to use ListAdapter, because this implementation have a more efficient way to compare the current list with the new list and update it. You can follow this example:
class MyAdapter: ListAdapter<ItemModel, MyAdapter.MyViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = FragmentFirstBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(getItem(position))
}
class MyViewHolder(
private val binding: FragmentFirstBinding
): RecyclerView.ViewHolder(binding.root) {
fun bind(item: ItemModel) {
// Here you can get the item values to put this values on your view
}
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ItemModel>() {
override fun areItemsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean {
// need a unique identifier to have sure they are the same item. could be a comparison of ids. In this case, that is just a list of strings just compare like this below
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean {
// compare the objects
return oldItem == newItem
}
}
}
}
In your fragment, you have a observer, that observe the value you want to sent to the adapter, right? When a update happen, you call the submitList sending the updated list and when the adapter receive this new list, the adapter will be responsible to update just the items that changed, because of your DIFF_CALLBACK implementation.
About the onClick item, you can wait for a callback on your adapter. Doing this:
class MyAdapter(
private val onItemClicked: (item: ItemModel) -> Unit
): ListAdapter<ItemModel, MyAdapter.MyViewHolder>(DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = FragmentFirstBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding, onItemClicked)
}
// ...
class MyViewHolder(
private val binding: FragmentFirstBinding,
private val onItemClicked: (item: ItemModel) -> Unit
): RecyclerView.ViewHolder(binding.root) {
fun bind(item: ItemModel) {
// ...
// Here you set the callback to a listener
binding.root.setOnClickListener {
onItemClicked.invoke(item)
}
}
}
// ...
}
As you can see, we will receive the callback on the Adapter constructor, then we send to the ViewHolder by constructor too. And on the ViewHolder bind we set the callback to a click listener.
On you fragment, you will have something like this:
class MyFragment: Fragment() {
private lateinit var adapter: MyAdapter
private val onItemClicked: (itemModel: ItemModel) -> Unit = { itemModel ->
// do something here when the item is clicked, like redirect to another activity
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = MyAdapter(onItemClicked)
}
}
I hope it helps you. Please, let me know if you need something more. I really appreciate helping.
I don't know about data binding specifically, but a typical way to do it is to let the Activity handle details like app navigation, and let the Adapter trigger that logic. A listener function is an easy way to do this:
// in your Adapter
var clickListener: ((YourData) -> ())? = null
// in your ViewHolder (make it an inner class so it can access the Adapter's
// fields, like the listener object and the stored data)
init {
clickableView.setOnClickListener {
// pass back whatever data here, if the listener needs to know
// what's been clicked. I'm just doing a lookup and passing
// the data item currently being displayed
clickListener?.invoke(
adapterData[bindingAdapterPosition]
)
}
}
// in your Activity, when setting up the adapter
adapter.clickListener = { whateverData ->
// do what you need to do in response to the click
}
So the Activity itself is defining that logic about actions that should be taken when a click happens - it's basically wiring up different parts of the app, so the Adapter doesn't need to be concerned with anything except taking data, displaying it, and informing a listener when specific interactions take place. That listener code (defined by the Activity) could navigate somewhere else, or update a database, or pass it to a networking component... the adapter doesn't need to know about that.
(The non-Kotlin way to do this would be to create an interface and have the Activity implement that, and pass itself as the listener/callback object, that kind of thing)
I'm making a screen similar to the image.
The data set in advance is taken from the Room DB and the data is set for each tab.
Each tab is a fragment and displays the data in a RecyclerView.
Each tab contains different data, so i set Tab to LiveData in ViewModel and observe it.
Therefore, whenever tabs change, the goal is to get the data for each tab from the database and set it in the RecyclerView.
However, even if I import the data, it is not set in RecyclerView.
I think the data comes in well even when I debug it.
This is not an adapter issue.
What am I missing?
WorkoutList
#Entity
data class WorkoutList(
#PrimaryKey(autoGenerate = true)
val id: Long = 0,
val chest: List<String>,
val back: List<String>,
val leg: List<String>,
val shoulder: List<String>,
val biceps: List<String>,
val triceps: List<String>,
val abs: List<String>
)
ViewModel
class WorkoutListViewModel(application: Application) : AndroidViewModel(application){
private var _part :MutableLiveData<BodyPart> = MutableLiveData()
private var result : List<String> = listOf()
private val workoutDao = WorkoutListDatabase.getDatabase(application).workoutListDao()
private val workoutListRepo = WorkoutListRepository(workoutDao)
val part = _part
fun setList(part : BodyPart) : List<String> {
_part.value = part
viewModelScope.launch(Dispatchers.IO){
result = workoutListRepo.getWorkoutList(part)
}
return result
}
}
Repository
class WorkoutListRepository(private val workoutListDao: WorkoutListDao) {
suspend fun getWorkoutList(part: BodyPart) : List<String> {
val partList = workoutListDao.getWorkoutList()
return when(part) {
is BodyPart.Chest -> partList.chest
is BodyPart.Back -> partList.back
is BodyPart.Leg -> partList.leg
is BodyPart.Shoulder -> partList.shoulder
is BodyPart.Biceps -> partList.biceps
is BodyPart.Triceps -> partList.triceps
is BodyPart.Abs -> partList.abs
}
}
}
Fragment
class WorkoutListTabPageFragment : Fragment() {
private var _binding : FragmentWorkoutListTabPageBinding? = null
private val binding get() = _binding!!
private lateinit var adapter: WorkoutListAdapter
private lateinit var part: BodyPart
private val viewModel: WorkoutListViewModel by viewModels()
companion object {
#JvmStatic
fun newInstance(part: BodyPart) =
WorkoutListTabPageFragment().apply {
arguments = Bundle().apply {
putParcelable("part", part)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let { bundle ->
part = bundle.getParcelable("part") ?: throw NullPointerException("No BodyPart Object")
}
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
_binding = FragmentWorkoutListTabPageBinding.inflate(inflater, container, false)
binding.apply {
adapter = WorkoutListAdapter()
rv.adapter = adapter
}
val result = viewModel.setList(part)
// Set data whenever tab changes
viewModel.part.observe(viewLifecycleOwner) { _ ->
// val result = viewModel.setList(part)
adapter.addItems(result)
}
return binding.root
}
} viewModel.part.observe(viewLifecycleOwner) { _ ->
adapter.addItems(result)
}
return binding.root
}
}
The problem you are seeing is that in setList you start an asynchronous coroutine on the IO thread to get the list, but then you don't actually wait for that coroutine to run but just return the empty list immediately.
One way to fix that would be to observe a LiveData object containing the list, instead of observing the part. Then, when the asynchronous task is complete
you can post the retrieved data to that LiveData. That would look like this in the view model
class WorkoutListViewModel(application: Application) : AndroidViewModel(application) {
private val _list = MutableLiveData<List<String>>()
val list: LiveData<List<String>>
get() = _list
// "part" does not need to be a member of the view model
// based on the code you shared, but if you wanted it
// to be you could do it like this, then
// call "viewModel.part = part" in "onCreateView". It does not need
// to be LiveData if it's only ever set from the Fragment directly.
//var part: BodyPart = BodyPart.Chest
// calling getList STARTS the async process, but the function
// does not return anything
fun getList(part: BodyPart) {
viewModelScope.launch(Dispatchers.IO){
val result = workoutListRepo.getWorkoutList(part)
_list.postValue(result)
}
}
}
Then in the fragment onCreateView you observe the list, and when the values change you add them to the adapter. If the values may change several times you may need to clear the adapter before adding the items inside the observer.
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
//...
// Set data whenever new data is posted
viewModel.list.observe(viewLifecycleOwner) { result ->
adapter.addItems(result)
}
// Start the async process of retrieving the list, when retrieved
// it will be posted to the live data and trigger the observer
viewModel.getList(part)
return binding.root
}
Note: The documentation currently recommends only inflating views in onCreateView and doing all other setup and initialization in onViewCreated - I kept it how you had it in your question for consistency.
I implemented a ListAdapter with DiffUtil and faced an issue when appending a new list. It overwrites instead of appending to old one. To solve issue i created a new project and populate it with some test data.
Here is my code:
MainActivity
private lateinit var binding: ActivityMainBinding
private val viewModel: ItemViewModel by lazy {
ItemViewModel()
}
private val adapter: ItemAdapter by lazy {
ItemAdapter()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel.getItems()
viewModel.items.observe(this, Observer { items ->
adapter.submitList(items)
})
binding.recyclerView.adapter = adapter
binding.fab.setOnClickListener {
viewModel.getItems(9)
}
}
ItemViewModel
class ItemViewModel: ViewModel() {
private val repository = FakeRepository()
private val _items: MutableLiveData<List<Item>> = MutableLiveData()
val items: LiveData<List<Item>> = _items
fun getItems(start: Int = 1) {
viewModelScope.launch {
val items = repository.getItems(start)
_items.value = items
/*val newItems = items.map { it.copy() }
_items.postValue(newItems)*/
}
}
}
ItemAdapter
class ItemAdapter: ListAdapter<Item, ItemAdapter.ViewHolder>(DiffUtilCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ItemRowBinding.inflate(LayoutInflater.from(parent.context),parent,false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
class ViewHolder(private val binding: ItemRowBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
binding.apply {
title.text = item.title
}
}
}
private class DiffUtilCallback: DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item) = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Item, newItem: Item) = oldItem == newItem
}
}
Item
data class Item(
val id: Int,
val title: String,
val timestamp: String
)
As per documentation:
Submits a new list to be diffed, and displayed.
If a list is already being displayed, a diff will be computed on a
background thread, which will dispatch Adapter.notifyItem events on
the main thread.
So, when you submit a new list via the LiveData observer, it's a brand new list to the adapter, and therefore it overwrites the current items not appending them.
If you want to append the current items, you can create a method in the adapter to consolidate the current list with the new one, and eventually submit it:
class ItemAdapter : ListAdapter<Item, ItemAdapter.ViewHolder>(DiffUtilCallback()) {
//......
fun appendList(list: List<Item>) {
val currentList = currentList.toMutableList() // get the current adapter list as a mutated list
currentList.addAll(list)
submitList(currentList)
}
}
And apply that to the observer callback in the activity:
viewModel.items.observe(this, Observer { items ->
// myAdapter.submitList(items) // send a brand new list
myAdapter.appendList(items) // Update the current list
})
I'm having trouble with deleting data from my Firestore collection when the user clicks a delete button in a recyclerview. I can delete it from the recyclerview without any problems, but I'm having trouble to make the connection between the adapter, the viewmodel and the repository that handles Firestore operations.
In my adapter, I remove the item the user clicked on from the recyclerview:
class ArticleAdapter : RecyclerView.Adapter<ArticleAdapter.ViewHolder>() {
var data = mutableListOf<Product>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
holder.bind(item)
holder.deleteButton.setOnClickListener {
data.removeAt(position)
notifyDataSetChanged()
}
} ...
The recyclerview is populated after a query to the Firestore collection in my viewmodel:
class ArticleViewModel(private val repository: ProductRepository) : ViewModel() {
var savedProducts: MutableLiveData<MutableList<Product>> = MutableLiveData<MutableList<Product>>()
init {
savedProducts = getProducts()
}
fun getProducts(): MutableLiveData<MutableList<Product>> {
repository.getProducts().addSnapshotListener(EventListener<QuerySnapshot> { value, e ->
if (e != null) {
savedProducts.value = null
return#EventListener
}
val savedProductsList: MutableList<Product> = mutableListOf()
for (doc in value!!) {
val item = doc.toObject(Product::class.java)
item.id = doc.id
savedProductsList.add(item)
}
savedProductsList.sortBy { i -> i.productName }
savedProducts.value = savedProductsList
})
return savedProducts
} }
In my Fragment, I'm then observing any changes that might happen to savedProducts:
class ArticleOverviewFragment : Fragment(), KodeinAware {
override val kodein: Kodein by kodein()
private val factory: ArticleViewModelFactory by instance()
private lateinit var viewModel: ArticleViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentArticleOverviewBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_article_overview, container, false)
viewModel = ViewModelProviders.of(this, factory).get(ArticleViewModel::class.java)
binding.viewModel = viewModel
val adapter = ArticleAdapter()
binding.recyclerViewGoods.adapter = adapter
viewModel.savedProducts.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
...
} }
Is there a way that I can observe/save the ID of the deleted item in my adapter and "transfer" that ID from the adapter to the UI where I call a function declared in the viewmodel whenever that field holding the ID is populated? Or should I directly access the viewmodel from the adapter? Somehow, that feels kinda wrong...
Declare one local variable
var removedPosition : Int ? = null
then update this variable into onClick event of deleteButton
holder.deleteButton.setOnClickListener {
data.removeAt(position)
removedPosition = position
notifyDataSetChanged()
}
Please make one method in Adapter (ArticleAdapter)
fun getRemoveItemPosition() : Int {
var position = removedPosition
return position;
}
which return the position of removed Item and call that method in UI(ArticleOverviewFragment) where you will require to get position of removed item from recyclerview
var removedItemPosition = adapter.getRemoveItemPosition()
Now you will get value of remove item Position using variable called removedItemPosition
So You can get Position of removed Item in UI where you can call a function declared in the viewmodel (ArticleViewModel) to delete particular item in firestore collection.
My app is displaying a list of various categories (herbs, side dishes, ..) in a RecyclerView. Depending on the category you clicked on, a new Activity with a new RecylcerView opens containing all the ingredients.
Right now I have an ArrayList which gets filled with the ingredients via ".add" depending on the choosen category.
The problem im facing right now is, that I want to implement an option for the user to add own Ingredients. I tried storing the ArrayList containing the ingredients in SharedPreferences by using Gson, but I couldn't manage to add elements, since it always overwrote the current list.
What would be the best way to store the ingredients? A room, sqlite, ..?
Without further explanation, the ingredient list will only contain about 70 items max.
Thanks in advance.
Edit:
CatList.kt
class CatList : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cat_list)
//Create List for categories
val cats = ArrayList<IngCat>()
//Fill categories
cats.add(IngCat(R.drawable.herbs, "Herbs"))
cats.add(IngCat(R.drawable.fluessiges, "Liquids"))
cats.add(IngCat(R.drawable.festes, "Solids"))
cats.add(IngCat(R.drawable.beilagen, "Sides"))
//Recyclerview
id_rv_CatList.layoutManager = LinearLayoutManager(this)
id_rv_CatList.adapter =
CatListAdapter(cats) {listItem, position -> //go to Ingredient List Activity
goToIngList(position, listItem.name)
}
//id_rv_CatList.addItemDecoration(DividerItemDecoration(this,DividerItemDecoration.HORIZONTAL))
//actionbar
val actionbar = supportActionBar
//set actionbar title
actionbar!!.title = "Ingredient - Categories"
}
private fun goToIngList(cat: Int, name: String){
val intent = Intent(this, IngList::class.java)
intent.putExtra("Category", cat)
intent.putExtra("Name", name)
startActivity(intent)
}
}
data class IngCat(var mImageResource:Int, var name:String)
IngList.kt
class IngList : AppCompatActivity() {
companion object {
var categoryChoosen : Int = 0
var catName : String = "Err"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_ing_list)
//Initilize Ingredient List
val ings :ArrayList<IngIng> = ArrayList()
//Get category and category name
categoryChoosen = intent.getIntExtra("Kategorie",0)
catName = intent.getStringExtra("Name")!!
when (categoryChoosen) {
0 -> {
ings.add(IngIng("https://doeel.com/images/thumbnails/1100/900/detailed /92/Turmeric_Powder___Holud_Gura__.png", "Turmeric Powder"))
}
1 -> ings.add(IngIng("https://www.miraherba.de/4923-large_default/bio-ghee-300-g.jpg", "Ghee"))
2 -> ings.add(IngIng("https://www.organicfacts.net/wp-content/uploads/coriander-1.jpg", "Coriander leaves"))
3 -> ings.add(IngIng("https://gbc-cdn-public-media.azureedge.net/img75602.1426x713.jpg", "Potatoes"))
}
//Actionbar Settings
setSupportActionBar(toolbar)
val actionbar = supportActionBar
actionbar!!.title = "Ingredients- $catName"
actionbar.setDisplayHomeAsUpEnabled(true)
//Recyclerview
id_rv_IngList.layoutManager = GridLayoutManager(this,2)
id_rv_IngList.adapter =
IngListAdapter(ings) {//ClickListener RecyclerView
Toast.makeText(this, "Item clicked: ${it.name}", Toast.LENGTH_SHORT).show()
}
//Actionbar
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.actionbar_ing_list, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.id_menu_action_add -> {
val intent = Intent(this, AddIngredient::class.java)
startActivity(intent)
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
}
IngListAdapter.kt
class IngListAdapter (private val ings: ArrayList<IngIng>, val clickListener: (IngIng)->Unit): RecyclerView.Adapter<RecyclerView.ViewHolder>(){
override fun getItemCount(): Int = ings.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val v: View = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_ing_list_item, parent, false)
return IngViewHolder(v)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
var currentItem = ings.get(position)
when (holder) {
is IngViewHolder -> {
holder.tvIngList.text = currentItem.name
// holder.ivIngImage.setImageResource(currentItem.mImageResource)
Picasso.get().load(currentItem.mImageResource).placeholder(R.drawable.ic_broken_image_black_200dp).error(R.drawable.ic_broken_image_red_24dp).into(holder.ivIngImage)
holder.cvIngCard.setOnClickListener{
clickListener(currentItem)
}
}
}
}
}
class IngViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val tvIngList: TextView = view.id_text_ing
val ivIngImage: ImageView = view.id_img_ing
val cvIngCard: MaterialCardView = view.id_cv_ing_list
}
I personally think Json/Gson in a SharedPreference is the easiest way to go if there are so few items. The way I would handle it is to store the list in memory at application startup, and persist the list back to the SharedPreference when the app is shut down. Also when the app gets stopped for good measure because you can't 100% be sure onDestroy will be called.
So first I'd make a class to store the data. If you were using Fragments that all are in the same Activity, you'd put this in a ViewModel. But since they are separate Activities, you need a singleton for them. (Google doesn't recommend using multiple Activities because it's hard to share data between them. But it's not impossible. It's what we did before Fragments.)
To do it as a singleton, you could have a class like this:
class IngredientsRepo private constructor (application: Application) {
companion object {
private val INSTANCE: IngredientsRepo? = null
fun getInstance(application: Application) =
INSTANCE ?: IngredientsRepo(application).also { INSTANCE = it }
private const KEY_JSON_PREF = "ingredientsJson"
}
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
val herbsList: MutableList<IngCat>
val liquidsList: MutableList<IngCat>
val solidsList: MutableList<IngCat>
val sidesList: MutableList<IngCat>
init {
val json = sharedPreferences.getString(KEY_JSON_PREF, null)
if (json == null) {
// initialize your list contents for the first time
} else {
// convert your json and fill the data into your lists
}
}
fun save {
val jsonString = // Convert your lists to Json
sharedPreferences.edit().putString(KEY_JSON_PREF, jsonString).apply()
}
}
This class becomes responsible for setting up your lists. You can retrieve it from any Activity with IngredientsRepo.getInstance(this) and you can add and remove items from the lists whenever you like. You can also call save on it whenever you like to persist the latest data. It's probably sufficient to do this in onStop() of any Activity that modifies the list.
More properly, the data in this class would only be exposed with immutable lists, and you'd add functions for adding and removing items, so only this class directly modifies the lists. I didn't want to overcomplicate the example, but it would be better for encapsulation to not have Activities (which are supposed to be pure UI components) directly modifying data structures.