I'm trying to figure out how Data Binding works with RecyclerView.
I've got Sound objects that have names. I bind these objects to ViewHolders and somehow, these names are displayed on the items of my RecyclerView list.
Here is the code (not mine, I took it from a book)
Layout File:
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="ru.vsevolod.zimin.beatbox.SoundViewModel"/>
</data>
<Button
android:layout_height="120dp"
android:text="#{viewModel.title}"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_width="match_parent"
tools:text="Sound Button"/>
</layout>
MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var beatBox: BeatBox
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
beatBox = BeatBox(assets)
beatBox.loadSounds()
val binding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.recyclerViewie.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = SoundAdapter()
}
}
private inner class SoundHolder (val binding: ListItemSoundBinding):
RecyclerView.ViewHolder(binding.root) {
init {
binding.viewModel = SoundViewModel()
}
fun bind(sound: Sound) {
binding.viewModel?.sound = sound
}
}
private inner class SoundAdapter: RecyclerView.Adapter<SoundHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SoundHolder {
val binding = DataBindingUtil.inflate<ListItemSoundBinding> (layoutInflater,
R.layout.list_item_sound, parent, false)
return SoundHolder(binding)
}
override fun onBindViewHolder(holder: SoundHolder, position: Int) {
val sound = beatBox.sounds[position]
holder.bind(sound)
}
override fun getItemCount(): Int {
return beatBox.sounds.size
}
}
}
BeatBox (creates and contains the list of Sound objects that are going to be bound to the ViewHolders):
private const val SOUNDS_FOLDER = "sample_sounds"
class BeatBox(private val assets: AssetManager) {
val sounds: List<Sound>
init {
sounds = loadSounds()
}
fun loadSounds(): List<Sound> {
val soundNames: Array<String>
try {
soundNames = assets.list(SOUNDS_FOLDER)!!
}catch(e: Exception){
Log.e(TAG, "Couldn't list assets",e)
return emptyList()
}
val sounds = mutableListOf<Sound>()
soundNames.forEach {
val sound = Sound(it)
sounds.add(sound)
}
return sounds
}
}
Sound (class for Sound objects):
class Sound(val name: String)
SoundViewModel:
class SoundViewModel: BaseObservable() {
var sound: Sound? = null
set(sound) {
field = sound
notifyChange()
}
#get : Bindable
var title: String? = null
get() = sound?.name
}
What I fail to understand is how exactly the titles of the Sound objects are hooked up to the ViewHolders. I also noticed that when I remove the getter from SoundViewModel like that:
Before:
#get : Bindable
var title: String? = null
get() = sound?.name
After:
#get : Bindable
var title = sound?.name
...the titles are no longer bound and I end up with a nameless list.
Could you please explain how this happens?
Thank you in advance!
Related
I got some categories from an api and trying to show them on a recycler view but it doesn't work for some reason.
Although the data appears correctly in the logcat, it is sent as null to the Category adapter.
This is the Main Activity (where I'm trying to show the data):
`
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val TAG = "MEALZ"
private lateinit var binding: ActivityMainBinding
private val viewModel:MealsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val adapter = CategoryAdapter(this)
binding.categoriesRv.adapter = adapter
viewModel.getMeals()
lifecycleScope.launch {
viewModel.categories.collect {
adapter.setData(it?.categories as List<Category>)
Log.d(TAG, "onCreate: ${it?.categories}")
}
}
}
}
`
This is Recycler Category Adapter :
`
class CategoryAdapter(private val context: Context?) :
RecyclerView.Adapter<CategoryAdapter.CategoryViewHolder>() {
private var categoryList: MutableList<Category?> = mutableListOf<Category?>()
inner class CategoryViewHolder(itemView: CategoryLayoutBinding) :
RecyclerView.ViewHolder(itemView.root) {
val name = itemView.categoryNameTv
val img = itemView.categoryIv
val des = itemView.categoryDesTv
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder {
val binding = CategoryLayoutBinding.inflate(LayoutInflater.from(context), parent, false)
return CategoryViewHolder(binding)
}
override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
var category = categoryList[position]
holder.name.text = category?.strCategory
holder.des.text = category?.strCategoryDescription
Glide.with(context as Context).load(category?.strCategoryThumb).into(holder.img)
}
override fun getItemCount(): Int {
return categoryList.size
}
fun setData(CategoryList: List<Category>) {
this.categoryList.addAll(CategoryList)
notifyDataSetChanged() //to notify adapter that new data change has been happened to adapt it
}
}
`
This is the View Model class:
#HiltViewModel
class MealsViewModel #Inject constructor(private val getMealsUseCase: GetMeals): ViewModel() {
private val TAG = "MealsViewModel"
private val _categories: MutableStateFlow<CategoryResponse?> = MutableStateFlow(null)
val categories: StateFlow<CategoryResponse?> = _categories
fun getMeals() = viewModelScope.launch {
try {
_categories.value = getMealsUseCase()
} catch (e: Exception) {
Log.d(TAG, "getMeals: ${e.message.toString()}")
}
}
}
you create your _categories with null as initial value, so first value of categories flow will be null and only second one will contain fetched data. As a workaround, you can check that data is not null:
viewModel.categories.collect {
if (it != null) {
adapter.setData(it?.categories as List<Category>)
Log.d(TAG, "onCreate: ${it?.categories}")
}
}
or introduce some kind of "loading" state
hi im currently using kotlin for my android project,as per instruction,i was told to make an apps that has recycleview to show list item and intent when you click on one of the list shown.
but i have this error when i want to run the app,the error was "No value passed for parameter 'ListUserAdapter'"
here is my code
ListUserAdapter.kt
class ListUserAdapter(private val ListUserAdapter: ArrayList<User>) : RecyclerView.Adapter<ListUserAdapter.ListViewHolder>() {
private lateinit var onItemClickCallBack: OnItemClickCallBack
fun setOnItemClickCallback(onItemClickCallback: OnItemClickCallBack) {
this.onItemClickCallBack = onItemClickCallback
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
val view: View = LayoutInflater.from(parent.context).inflate(R.layout.row_user, parent, false)
return ListViewHolder(view)
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
val (name, username) = ListUserAdapter[position]
holder.tvName.text = name
holder.tvUserName.text= username
holder.itemView.setOnClickListener {
onItemClickCallBack.onItemClicked(ListUserAdapter[holder.adapterPosition])
}
}
override fun getItemCount(): Int = ListUserAdapter.size
class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvName: TextView = itemView.findViewById(R.id.tv_username)
var tvUserName: TextView = itemView.findViewById(R.id.tv_name)
}
interface OnItemClickCallBack {
fun onItemClicked(data : User)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var adapter: ListUserAdapter
private lateinit var dataName: Array<String>
private lateinit var dataUsername: Array<String>
private lateinit var dataLocation: Array<String>
private lateinit var dataRepo: Array<String>
private lateinit var dataCompany: Array<String>
private lateinit var dataFollowers: Array<String>
private lateinit var dataFollowing: Array<String>
private lateinit var dataPhoto: TypedArray
private var users = arrayListOf<User>()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setAdapter()
prepare()
addItem()
}
private fun setAdapter() {
adapter = ListUserAdapter() //error in here
with(binding) {
rvList.adapter = adapter
rvList.layoutManager =
GridLayoutManager(this#MainActivity, 2, GridLayoutManager.HORIZONTAL, false)
rvList.setHasFixedSize(true)
}
adapter.setOnItemClickCallback(object : ListUserAdapter.OnItemClickCallBack{
override fun onItemClicked(user: User) {
val intent = Intent(this#MainActivity, DetailActivity::class.java)
intent.putExtra(DetailActivity.KEY_USER, user)
startActivity(intent)
}
})
}
private fun prepare() {
dataName = resources.getStringArray(R.array.name)
dataUsername = resources.getStringArray(R.array.username)
dataPhoto = resources.obtainTypedArray(R.array.avatar)
dataLocation = resources.getStringArray(R.array.location)
dataRepo = resources.getStringArray(R.array.repository)
dataCompany = resources.getStringArray(R.array.company)
dataFollowers = resources.getStringArray(R.array.followers)
dataFollowing = resources.getStringArray(R.array.following)
}
private fun addItem() {
for (position in dataName.indices) {
val user = User(
dataUsername[position],
dataName[position],
dataLocation[position],
dataCompany[position],
dataRepo[position],
dataFollowers[position],
dataFollowing[position],
dataPhoto.getResourceId(position, -1)
)
users.add(user)
}
}
}
DetailActivity.kt
class DetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
setData()
}
private fun setData() {
val dataUser = intent.getParcelableExtra<User>(KEY_USER) as User
with(binding) {
Glide.with(root)
.load(dataUser.photo)
.circleCrop()
.into(ivDetailAvatar)
}
}
companion object {
const val KEY_USER = "key_user"
}
}
just you need to replace adapter = ListUserAdapter() //error in here with adapter = ListUserAdapter(users) then your problem solve
You're just forgetting the constructor parameter :)
Your adapter class needs to receive a ArrayList of User to be instantiated, you already have it in your Activity, you just need to pass it as the constructor parameter :)
I would also rename the parameter name to something like users or usersList instead of `ListUserAdapter but because currently it is misleading, since this parameter is not an adapter, it is a list of users.
Just change the line with error to
adapter = ListUserAdapter(users)
But I think it is best for you to first call prepare() and addItem() and then instantiate your adapter. Or you can instantiate your adapter, but also create a addItems function, since it is a best practice for adapters.
fun addItems(users: List<User>) {
this.ListUserAdapter.addAll(users)
notifyDataSetChanged()
}
and use it after setting everything up inside onCreate doing something like
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setAdapter()
prepare()
addItem()
adapter.addItems(users)
}
but you could probably reorganize these methods to improve readability as well, but it will work :)
When I run the app, the fragments content is blank. Even though the log statements show, the list is populated. I tried implementing a favorite post feature. You can add/remove a favorite post to your list. This works fine.
The goal:
I want to display the favorite posts in FavoritePostsOverViewFragment. Using a recyclerView.
I'm also trying to follow MVVM architecture. Using a Room database. (no API at this point)
The problem(s):
Working with the 2 different objects seems a bit weird the way I do it right now. But it is populated at the moment
Please refer to the part "How I am getting the posts based on if they have been favorite by a user" Is there a less complex way of writing this?
The Binding Adapter is null / empty, not displaying the posts.
I am using the Adapter already in another fragment, it works fine there. I can see a list of posts and use the click listeners. So In my thoughts, I eliminated the adapter as a problem for this case.
The two data classes used:
data class Post(
var Id: Long = 0L,
var Text: String = "",
var Picture: Bitmap? = null,
var Link: String = "",
var UserId: String = "",
var UserEmail: String = ""
)
data class Favorite(
var Id: Long = 0L,
var UserId: String = "",
var PostId: Long = 0L
)
The Adapter
lass PostAdapter(val clickListener: PostListener, val favoriteListener: FavoriteListener) :
ListAdapter<Post, ViewHolder>(PostDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(clickListener, favoriteListener, item)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
}
class ViewHolder(val binding: PostListItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(clickListener: PostListener, favoriteListener: FavoriteListener, item: Post) {
binding.post = item
binding.clickListener = clickListener
binding.favoriteListener = favoriteListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
println(layoutInflater.toString())
val binding = PostListItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
class PostDiffCallback : DiffUtil.ItemCallback<Post>() {
override fun areItemsTheSame(oldItem: Post, newItem: Post): Boolean {
return oldItem.Id == newItem.Id
}
override fun areContentsTheSame(oldItem: Post, newItem: Post): Boolean {
return oldItem == newItem
}
}
class PostListener(val clickListener: (post: Post) -> Unit) {
fun onClick(post: Post) = clickListener(post)
}
class FavoriteListener(val clickListener: (post: Post) -> Unit) {
fun onClick(post: Post) = clickListener(post)
}
How I am getting the posts based on if they have been favorite by a user.
class PostRepository(private val faithDatabase: FaithDatabase) {
suspend fun getUserFavs(): List<Long> {
return withContext(Dispatchers.IO) {
faithDatabase.favoriteDatabaseDao.getUserFavorites(CredentialsManager.cachedUserProfile?.getId()!!)
}
}
suspend fun getFavos(): LiveData<List<Post>> {
val _items: MutableLiveData<List<Post>> = MutableLiveData(listOf())
val items: LiveData<List<Post>> = _items
val postIds: List<Long>
var dbPost: DatabasePost
withContext(Dispatchers.IO) {
postIds = getUserFavs()
}
for (id in postIds) {
withContext(Dispatchers.IO) {
dbPost = faithDatabase.postDatabaseDao.get(id)
}
val post = Post(
Text = dbPost.Text,
UserId = dbPost.UserId,
UserEmail = dbPost.UserEmail,
Link = dbPost.Link,
Picture = dbPost.Picture,
Id = dbPost.Id
)
_items.value = _items.value?.plus(post) ?: listOf(post)
}
Timber.i("items= " + items.value!!.size)
/*this logs=
I/PostRepository: items= 2*/
return items
}
My FavoritePostOverViewModel
class FavoritePostsOverviewViewModel(val database: PostDatabaseDao, app: Application) :
AndroidViewModel(app) {
private val db = FaithDatabase.getInstance(app.applicationContext)
private val postRepository = PostRepository(db)
var posts: LiveData<List<Post>>? = null
init {
viewModelScope.launch {
posts = repository.getFavos()
Timber.i(posts!!.value.toString())
/* this logs=
I/FavoritePostsOverviewViewModel: [Post(Id=1, Text=Name, Picture=android.graphics.Bitmap#ef3b553, Link=Add your link here, UserId=auth0|62cc0d4441814675a5906130, UserEmail=jdecorte6#gmail.com), Post(Id=4, Text=test, Picture=android.graphics.Bitmap#35ae90, Link=www.google.com, UserId=auth0|62cc0d4441814675a5906130, UserEmail=jdecorte6#gmail.com)]*/
}
}
my FavoritePostsOverViewFragment
class FavoritePostsOverViewFragment : Fragment() {
lateinit var binding: FragmentFavoritePostsBinding
private lateinit var favoritePostsOverviewViewModel: FavoritePostsOverviewViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// setup the db connection
val application = requireNotNull(this.activity).application
val dataSource = FaithDatabase.getInstance(application).postDatabaseDao
// create the factory + viewmodel
val viewModelFactory = FavoritePostsOverviewViewModelFactory(dataSource, application)
favoritePostsOverviewViewModel =
ViewModelProvider(this, viewModelFactory)[FavoritePostsOverviewViewModel::class.java]
binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_favorite_posts, container, false)
// Giving the binding access to the favoritePostsOverviewViewModel
binding.favoritePostsOverviewViewModel = favoritePostsOverviewViewModel
// Allows Data Binding to Observe LiveData with the lifecycle of this Fragment
binding.lifecycleOwner = this
// Sets the adapter of the PostAdapter RecyclerView with clickHandler lambda that
// tells the viewModel when our property is clicked
binding.postList.adapter = PostAdapter(PostListener {
favoritePostsOverviewViewModel.displayPropertyDetails(it)
}, FavoriteListener {
favoritePostsOverviewViewModel.FavoriteClick(it)
})
return binding.root
}
I have a Binding Adapter
#BindingAdapter("listData")
fun bindRecyclerViewPost(recyclerView: RecyclerView, data: List<Post>?) {
if (data.isNullOrEmpty()) {
return
}
val adapter = recyclerView.adapter as PostAdapter
adapter.submitList(data)
}
Used in the XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="favoritePostsOverviewViewModel"
type="com.example.ep3_devops_faith.ui.post.favorites.FavoritePostsOverviewViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/post_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:padding="6dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:listData="#{favoritePostsOverviewViewModel.posts}"
tools:listitem="#layout/post_list_item"
tools:itemCount="16"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
referenced articles:
Android BindingAdapter order of execution?
LiveData Observer in BindingAdapter
https://developer.android.com/topic/architecture
https://developer.android.com/topic/libraries/data-binding/binding-adapters
https://play.kotlinlang.org/hands-on/Introduction%20to%20Coroutines%20and%20Channels/01_Introduction
try changing this line
app:listData="#{favoritePostsOverviewViewModel.posts}"
to
app:listData="#{favoritePostsOverviewViewModel.posts.value}"
I guess, you are binding list of posts in your binding adapter and you are passing LiveData<List>
First of all sorry for my bad English.
I'm trying to receive Data from my self-written Python Backend(REST-API) in my Android APP.
ApiService.kt:
private const val Base_URL = "http://192.168.178.93:5000/api/"
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(Base_URL)
.build()
interface TodoApiService{
#GET("todo")
suspend fun getToDo(): List<ToDo>
}
object ToDoApi{
val retrofitService : TodoApiService by lazy {
retrofit.create(TodoApiService::class.java)
}
}
MainActivityViewModel.kt:
class MainActivityViewModel : ViewModel() {
of the most recent request
private val _status = MutableLiveData<String>()
val status: LiveData<String> = _status
private val _toDo = MutableLiveData<List<ToDo>>()
val toDo: LiveData<List<ToDo>> = _toDo
init {
getToDo()
}
private fun getToDo() {
viewModelScope.launch{
try {
_toDo.value = ToDoApi.retrofitService.getToDo()
_status.value = "Success $_toDo"
}catch (e: Exception){
_status.value = "Failure ${e.message}"
}
}
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>
<androidx.recyclerview.widget.RecyclerView
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/recycler_view"
tools:listitem="#layout/todo_item"/>
</FrameLayout>
todo_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="#+id/text_view_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"/>
</RelativeLayout>
</androidx.cardview.widget.CardView>
TodoAdapter.kt
class TodoAdapter(private val context: Context, private val Items: List<ToDo>):RecyclerView. Adapter<TodoAdapter.TodoViewHolder>(){
class TodoViewHolder(private val view: View) : RecyclerView.ViewHolder(view){
val textView: TextView = view.findViewById(R.id.text_view_name)
}
override fun getItemCount() = Items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.todo_item, parent, false)
return TodoViewHolder(adapterLayout)
}
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val ToDo = Items.get(position)
holder.textView.text = context.resources.getString(ToDo.Id.toInt())
}
}
MainActivity.kt:
class MainActivity : AppCompatActivity() {
private val viewModel = MainActivityViewModel()
private lateinit var binding: ActivityMainBinding
private lateinit var linearLayoutManager: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
linearLayoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = linearLayoutManager
binding.recyclerView.adapter = TodoAdapter(this, viewModel.toDo.value)
}
}
I know that I need an adapter to connect my LiveData to my recyclerView. But I'm not able to implement it right. Android studio tells me I cant use my MutableLiveData<List> for my Adapter that just only needs a normal List(Required: List Found: List?. I cant Cast cause the data could be null.
Your use of LiveData is incorrect. You use it as a simple variable, passing its current value (which is null) to the adapter.
LiveData is intended for data stream and need to be observed.
I like to add a setter for the data on the Adapter class:
fun setData(data: List<ToDo>) {
Items = data
notifyDataSetChanged()
}
And in the Activity, observe the ViewModel's live data and update the adapter when new data arrives:
class MainActivity : AppCompatActivity() {
private lateinit var adapter: TodoAdapter
override fun onCreate(savedInstanceState: Bundle?) {
...
adapter = TodoAdapter(this)
viewModel.todo.observe(this, { todos ->
adapter.setData(todos)
}
}
...
}
Now when you set value to your LiveData in the ViewModel, the adapter will be notified.
This is because you are sending a possible nullable list from the activity to the Adapter. You must do something like this:
binding.recyclerView.adapter = TodoAdapter(this, viewModel.toDo?.value ?: listOf())
It is a note taking app. I am using Cloud Firestore to store data. Data got added into database but recycler view is not showing anything. Below are the code snippets.
My Data Model Class:
class Notes {
var id:String?= null
var title: String? = null
var description: String? = null
var created: Timestamp? =null
constructor() {}
constructor(id:String,title: String?, description:String?,created:Timestamp?) {
this.id=id
this.title = title
this.description=description
this.created=created
}
}
AddNotesActivity
class AddNoteActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_note)
btn_add_note.setOnClickListener{
val noteTitle = note_title.text.toString()
val noteDescription =note_description.text.toString()
if(noteTitle.isNotEmpty() && noteDescription.isNotEmpty()){
addNotes(noteTitle,noteDescription)
Toast.makeText(this,"note added successfully",Toast.LENGTH_SHORT).show()
onBackPressed()
}
}
}
private fun addNotes(title:String, description:String){
val currentUserId = FirebaseAuth.getInstance().currentUser!!.uid
var note = Notes(currentUserId,title,description, Timestamp(Date()))
FirebaseFirestore.getInstance().collection("notes").add(note).addOnSuccessListener {
Log.i("AddNoteActivity","Note added")
}
}
}
NotesActivity(showing recyclerview):
class NotesActivity : AppCompatActivity() {
lateinit var notesAdapter: NotesAdapter
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_notes)
recyclerView = findViewById(R.id.rv_notes)
setUpRecyclerView()
floating_btn.setOnClickListener {
startActivity(Intent(this, AddNoteActivity::class.java))
}
}
private fun setUpRecyclerView() {
val query:Query= FirebaseFirestore.getInstance().collection("notes").whereEqualTo("id",FirebaseAuth.getInstance().currentUser!!.uid)
val options:FirestoreRecyclerOptions<Notes> = FirestoreRecyclerOptions.Builder<Notes>().setQuery(query,Notes::class.java).build()
notesAdapter = NotesAdapter(options)
recyclerView.adapter = notesAdapter
notesAdapter!!.startListening()
}
}
Adapter class:
class NotesAdapter(options:FirestoreRecyclerOptions<Notes>):FirestoreRecyclerAdapter<Notes,NotesAdapter.MyViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotesAdapter.MyViewHolder {
val itemView= LayoutInflater.from(parent.context).inflate(R.layout.each_note_view,parent,false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, p1: Int, NotesModel: Notes) {
holder.title.text = NotesModel.title
holder.description.text= NotesModel.description
val date=DateFormat.getDateInstance(DateFormat.MEDIUM).format(NotesModel.created)
holder.date.text = date.toString()
}
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val title : TextView = itemView.title_text
val description: TextView =itemView.description_text
val date : TextView =itemView.date_created
}
}
But when getting data from querysnapshot, logcat is showing perfect data from firestore database:
val query = FirebaseFirestore.getInstance().collection("notes").whereEqualTo("id", FirebaseAuth.getInstance().currentUser!!.uid).get().addOnSuccessListener {
val doc = it.documents
for (i in doc) {
Log.i("NotesActivity", i.data.toString())
}
}
Logcat: {created=Timestamp(seconds=1611116973, nanoseconds=14000000),
description=day, id=wLxCTMLGZpaWNs1b8Uhf3HoRUgz2, title=go}
I spend two days on this, but not getting any solution. I would be thankful if anybody can solve the issue.
Below is XML file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_notes"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="7"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/floating_btn"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginEnd="8dp"
android:backgroundTint="#color/light_blue"
android:src="#drawable/floating_btn"/>