I'm using Tmdb API and displaying it in a RecyclerView. The RecyclerView is not scrolling smoothly initially but then is working fine. I've tried to change Recyclerview height to 0dp or match_parent but it's still the same. I've tried also android:nestedScrollingEnabled="true". Please help me to fix this problem in the RecyclerView. Thank you in advance.
fragment_add.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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.AddFragment">
<androidx.appcompat.widget.AppCompatAutoCompleteTextView
android:id="#+id/fragment_add_movieSeriesACT"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:background="#drawable/edit_text_background"
android:hint="#string/movie_or_series"
android:imeOptions="actionDone"
android:inputType="textEmailAddress"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:textColor="#color/colorPrimary"
android:textColorHint="#color/colorEight"
android:textCursorDrawable="#null"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/fragment_add_mainRV"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/fragment_add_movieSeriesACT" />
<ProgressBar
android:id="#+id/fragment_add_mainPB"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:indeterminateTint="#color/colorOne"
android:indeterminateTintMode="src_atop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
AddFragment.kt
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import com.martiandeveloper.muuvi.R
import com.martiandeveloper.muuvi.adapter.RecyclerViewMovieAdapter
import com.martiandeveloper.muuvi.viewmodel.AddViewModel
import kotlinx.android.synthetic.main.fragment_add.*
class AddFragment : Fragment() {
private lateinit var viewModel: AddViewModel
private val adapter = RecyclerViewMovieAdapter(arrayListOf())
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_add, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
}
private fun initUI() {
viewModel = ViewModelProviders.of(this).get(AddViewModel::class.java)
setRecyclerView()
observe()
viewModel.refreshData()
}
private fun setRecyclerView() {
fragment_add_mainRV.layoutManager = LinearLayoutManager(context)
fragment_add_mainRV.adapter = adapter
}
private fun observe() {
viewModel.movieList.observe(viewLifecycleOwner, Observer { movieList ->
movieList?.let {
adapter.updateMovieList(it)
}
})
viewModel.isError.observe(viewLifecycleOwner, Observer { isError ->
isError?.let {
setProgress(1F, true, View.GONE)
if (it) {
setToast(resources.getString(R.string.something_went_wrong_please_try_again_later))
}
}
})
viewModel.isLoading.observe(viewLifecycleOwner, Observer { isLoading ->
isLoading?.let {
if (it) {
setProgress(.5F, false, View.VISIBLE)
} else {
setProgress(1F, true, View.GONE)
}
}
})
}
private fun setProgress(alpha: Float, enable: Boolean, visible: Int) {
fragment_add_movieSeriesACT.alpha = alpha
fragment_add_mainRV.alpha = alpha
fragment_add_movieSeriesACT.isEnabled = enable
fragment_add_mainPB.visibility = visible
}
private fun setToast(text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
}
}
AddViewModel.kt
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.martiandeveloper.muuvi.model.MovieResult
import com.martiandeveloper.muuvi.model.Movie
import com.martiandeveloper.muuvi.service.MovieService
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.observers.DisposableSingleObserver
import io.reactivex.schedulers.Schedulers
class AddViewModel : ViewModel() {
private val movieService = MovieService()
private val disposable = CompositeDisposable()
val movieList = MutableLiveData<List<Movie>>()
val isError = MutableLiveData<Boolean>()
val isLoading = MutableLiveData<Boolean>()
fun refreshData() {
isLoading.value = true
disposable.add(
movieService.getData().subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSingleObserver<MovieResult>() {
override fun onSuccess(t: MovieResult) {
movieList.value = t.results
isError.value = false
isLoading.value = false
}
override fun onError(e: Throwable) {
isError.value = true
isLoading.value = false
}
})
)
}
}
RecyclerViewMovieAdapter.kt
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.martiandeveloper.muuvi.R
import com.martiandeveloper.muuvi.model.Movie
import kotlinx.android.synthetic.main.recyclerview_movie_item.view.*
class RecyclerViewMovieAdapter(private val movieList: ArrayList<Movie>) :
RecyclerView.Adapter<RecyclerViewMovieAdapter.RecyclerViewMovieViewHolder>() {
lateinit var context: Context
class RecyclerViewMovieViewHolder(var view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerViewMovieViewHolder {
context = parent.context
val view = LayoutInflater.from(context)
.inflate(R.layout.recyclerview_movie_item, parent, false)
return RecyclerViewMovieViewHolder(view)
}
override fun getItemCount(): Int {
return movieList.size
}
override fun onBindViewHolder(holder: RecyclerViewMovieViewHolder, position: Int) {
holder.view.recyclerview_movie_item_movieTitleMTV.text =
movieList[position].movieTitle
holder.view.recyclerview_movie_item_movieVoteAverageMTV.text =
movieList[position].movieVoteAverage.toString()
Glide.with(context)
.load("https://image.tmdb.org/t/p/w300${movieList[position].moviePosterPath}")
.into(holder.view.recyclerview_movie_item_moviePosterIV)
}
fun updateMovieList(newMovieList: List<Movie>) {
movieList.clear()
movieList.addAll(newMovieList)
notifyDataSetChanged()
}
}
recyclerview_movie_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<androidx.cardview.widget.CardView
android:layout_width="80dp"
android:layout_height="80dp"
app:cardBackgroundColor="#android:color/transparent"
app:cardCornerRadius="10dp"
app:cardElevation="0dp">
<ImageView
android:id="#+id/recyclerview_movie_item_moviePosterIV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="#string/app_name"
android:scaleType="centerCrop"
app:srcCompat="#drawable/default_user_image" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="#+id/recyclerview_movie_item_movieTitleMTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/colorPrimary"
android:textSize="18sp"
android:textStyle="bold" />
<com.google.android.material.textview.MaterialTextView
android:id="#+id/recyclerview_movie_item_movieVoteAverageMTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/colorPrimaryDark" />
</LinearLayout>
</LinearLayout>
I believe this is due to size of the image , I had similar issue try to fit the image you obtain using glide into imageview, I have added centerCrop():
Glide.with(context).load("https://image.tmdb.org/t/p/w300${movieList[position].moviePosterPath}") .centerCrop().into(holder.view.recyclerview_movie_item_moviePosterIV);
If this does not work try to change imageview scaletype or centerCrop() to fitCenter()
Related
I'm hoping to create a checkbox for items that I have in a list.
I was looking at applying this by using a boolean value for my items i.e if attribute 'favourite' = true, then checkbox is highlighted and vice versa if false.
Unfortunately, so far I am unsure of the logic for this and how to connect it to my view (Checkbox).
My view is as follows:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
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="wrap_content"
android:layout_marginBottom="8dp"
android:elevation="24dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="#+id/imageIcon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:contentDescription="#string/change_hike_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/hikeName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAlignment="viewStart"
android:textSize="30sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.048"
app:layout_constraintStart_toEndOf="#+id/imageIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="A Title" />
<TextView
android:id="#+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.052"
app:layout_constraintStart_toEndOf="#+id/imageIcon"
app:layout_constraintTop_toBottomOf="#id/hikeName"
tools:text="A Description" />
<CheckBox
android:id="#+id/favourite"
style="?android:attr/starStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Adapter:
package org.wit.hikingtrails.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import org.wit.hikingtrails.databinding.CardHikeBinding
import org.wit.hikingtrails.models.HikeModel
interface HikeListener {
fun onHikeClick(hike: HikeModel)
}
class HikeAdapter constructor(private var hikes: List<HikeModel>,
private val listener: HikeListener) :
RecyclerView.Adapter<HikeAdapter.MainHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainHolder {
val binding = CardHikeBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return MainHolder(binding)
}
override fun onBindViewHolder(holder: MainHolder, position: Int) {
val hike = hikes[holder.adapterPosition]
holder.bind(hike, listener)
}
override fun getItemCount(): Int = hikes.size
class MainHolder(private val binding : CardHikeBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(hike: HikeModel, listener: HikeListener) {
binding.hikeName.text = hike.name
binding.description.text = hike.description
binding.favourite.text = hike.favourite.toString()
if (hike.image != ""){
Picasso.get()
.load(hike.image)
.resize(200, 200)
.into(binding.imageIcon)
}
binding.root.setOnClickListener { listener.onHikeClick(hike) }
}
}
}
View:
package org.wit.hikingtrails.views.hikeList
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import org.wit.hikingtrails.R
import org.wit.hikingtrails.adapters.HikeAdapter
import org.wit.hikingtrails.adapters.HikeListener
//import org.wit.hikingtrails.databinding.ActivityHikeListBinding
import org.wit.hikingtrails.main.MainApp
import org.wit.hikingtrails.models.HikeModel
import android.content.Intent
import android.view.*
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.wit.hikingtrails.databinding.ActivityHikeListBinding
import org.wit.hikingtrails.views.BaseView
import timber.log.Timber
class HikeListView : BaseView(), HikeListener {
lateinit var app: MainApp
lateinit var binding: ActivityHikeListBinding
lateinit var presenter: HikeListPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHikeListBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.toolbar.title = title
setSupportActionBar(binding.toolbar)
presenter = HikeListPresenter(this)
val layoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = layoutManager
updateRecyclerView()
}
// override fun showHikes(hikes: List<HikeModel>) {
// recyclerView.adapter = HikeAdapter(hikes, this)
// recyclerView.adapter?.notifyDataSetChanged()
// }
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onResume() {
//update the view
super.onResume()
updateRecyclerView()
binding.recyclerView.adapter?.notifyDataSetChanged()
Timber.i("recyclerView onResume")
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item?.itemId) {
R.id.item_add -> presenter.doAddHike()
R.id.item_map -> presenter.doShowHikesMap()
R.id.logout -> presenter.doLogout()
}
return super.onOptionsItemSelected(item)
}
override fun onHikeClick(hike: HikeModel) {
presenter.doEditHike(hike)
}
private fun updateRecyclerView(){
GlobalScope.launch(Dispatchers.Main){
binding.recyclerView.adapter =
HikeAdapter(presenter.getHikes(), this#HikeListView)
}
}
}
Add favorite property to your HikeModel like this
data class HikeModel(
... code
// use mutable property bcs you will update it later when checkbox is checked or uncheck.
var favorite: Boolean = false // false by default
)
and then
fun bind(hike: HikeModel, listener: HikeListener) {
...
binding.favourite.isChecked = hike.favorite
binding.favourite.setOnClickListener {
hike.favorite = binding.favourite.isChecked
listener.onHikeClick(hike)
}
...
}
I am trying to show a list of transactions in a recycler view.
Each transaction is represented by a CardView inside a constraint layout (see item_transaction.xml).
Somehow, the functions of my RecyclerView Adapter (onCreateViewHolder, onBindViewHolder and getItemCount) are never called (logs are never displayed - I removed most of them so the code is easier to read).
Therefore, the RecyclerView content doesn't display on my app.
It is also worth pointing out that I have a list of transactions called data in my adapter. Whenever I set the data in the list, it does update. No issues there.
💡 I know notifyDataSetChanged() isn't clean, I'll change it later on when I manage to get everything working.
Here is a preview of what I want so you can grasp the idea better :
EDIT : My fragment displays normally and has no problem executing code.
Hang on tight because there is a lot of code :
package com.example.manage.manageit.database
class Transaction(
var value: Long = 0,
var note: String = "",
var budget: String = "",
var creationTime: Long = System.currentTimeMillis()
)
Transaction.kt
package com.example.manage.manageit.home
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.example.manage.manageit.databinding.ActivityHomeBinding
import androidx.fragment.app.Fragment
import com.example.manage.manageit.R
import com.example.manage.manageit.about.AboutFragment
import com.example.manage.manageit.budget.TransactionFragment
class HomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityHomeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_home)
binding.bottomNavigation.menu.getItem(0).isCheckable = true
setFragment(HomeFragment())
binding.bottomNavigation.setOnItemSelectedListener {menu ->
when(menu.itemId){
R.id.homeFragmentButton -> {
setFragment(HomeFragment())
true
}
R.id.transactionFragmentButton -> {
setFragment(TransactionFragment())
true
}
R.id.aboutFragmentButton -> {
setFragment(AboutFragment())
true
}
else -> false
}
}
}
private fun setFragment(fr : Fragment){
val frag = supportFragmentManager.beginTransaction()
frag.replace(R.id.myNavHostFragment,fr)
frag.commit()
}
}
HomeActivity.kt : It contains a BottomNavigationView and my fragment.
package com.example.manage.manageit.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.manage.manageit.R
import com.example.manage.manageit.adapters.TransactionAdapter
import com.example.manage.manageit.database.Transaction
import com.example.manage.manageit.databinding.FragmentHomeBinding
import java.util.logging.Logger
class HomeFragment : Fragment() {
private var logger = Logger.getLogger(HomeFragment::class.java.name)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentHomeBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_home,
container,
false
)
val recyclerView: RecyclerView = binding.transactionList
val transactionAdapter = TransactionAdapter()
recyclerView.adapter = transactionAdapter
recyclerView.layoutManager = LinearLayoutManager(context)
transactionAdapter.data = listOf(
Transaction(10, "Transaction note", "Groceries"),
Transaction(20, "Transaction note", "Groceries"),
Transaction(30, "Transaction note", "Groceries")
)
return inflater.inflate(R.layout.fragment_home, container, false)
}
}
HomeFragment.kt
package com.example.manage.manageit.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.manage.manageit.R
import com.example.manage.manageit.database.Transaction
import java.util.logging.Logger
class TransactionAdapter : RecyclerView.Adapter<TransactionViewHolder>() {
private var logger = Logger.getLogger(this::class.java.name)
var data = listOf<Transaction>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransactionViewHolder {
logger.info("adapter : onCreateViewHolder()")
return TransactionViewHolder.from(parent)
}
override fun onBindViewHolder(holder: TransactionViewHolder, position: Int) {
val item = this.data[position]
holder.bind(item)
}
override fun getItemCount(): Int {
logger.info("adapter : getItemCount()")
return this.data.size
}
}
class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val icon: ImageView = itemView.findViewById(R.id.transaction_icon)
private val note: TextView = itemView.findViewById(R.id.transaction_note)
private val value: TextView = itemView.findViewById(R.id.transaction_value)
fun bind(item: Transaction) {
icon.setImageResource(R.drawable.ic_dollar_sign)
note.text = item.note
value.text = item.value.toString()
}
companion object {
fun from(parent: ViewGroup): TransactionViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.item_transaction, parent, false)
return TransactionViewHolder(view)
}
}
}
TransactionAdapter.kt
<?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"
tools:context="com.example.manage.manageit.home.HomeActivity">
<RelativeLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/myNavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"/>
</LinearLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#android:color/black"
android:paddingVertical="6dp"
app:itemIconSize="28dp"
app:itemIconTint="#color/nav_bar_item"
app:itemRippleColor="#color/ripple_color"
app:itemTextColor="#color/nav_bar_item"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_navigation_menu" />
</RelativeLayout>
</layout>
activity_home.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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black"
android:fillViewport="true"
tools:context="com.example.manage.manageit.home.HomeFragment">
<TextView
android:id="#+id/homeActivityTitle"
style="#style/activityTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/medium_margin"
android:layout_marginTop="#dimen/medium_margin"
android:layout_marginEnd="#dimen/medium_margin"
android:text="#string/home_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/transaction_list"
android:layout_width="match_parent"
android:layout_height="400dp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="#+id/floatingActionButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/homeActivityTitle" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="#dimen/medium_margin"
android:layout_marginBottom="#dimen/very_big_margin"
android:backgroundTint="#color/secondaryColor"
android:clickable="true"
android:contentDescription="#string/add_button_description"
android:src="#drawable/ic_plus"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:maxImageSize="32dp"
tools:ignore="RedundantDescriptionCheck" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
app:cardBackgroundColor="#android:color/transparent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#drawable/transaction">
<ImageView
android:id="#+id/transaction_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:contentDescription="Icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_dollar_sign"
app:tint="#android:color/white" />
<TextView
android:id="#+id/transaction_note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:text="Transaction note"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#+id/transaction_icon"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/transaction_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="30dp"
android:text="55€"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
item_transaction.xml : Basically a card view with an image and two text views (the transaction "note" and value).
In onCreateView, you have created your view layout twice. The first one you did using DataBindingUtil and that's the one whose RecyclerView you set up. But then you let that whole layout go back to the garbage collector because you create a brand new layout using layoutInflater and return that layout on the last line of onCreateView.
Technically, you should not be setting up views in onCreateView anyway. It should be done in onViewCreated(), although I don't think it makes much difference. However, since Fragment provides a constructor that can automatically inflate a provided layout ID, I think it's cleaner anyway to eliminate onCreateView() entirely, like this:
class HomeFragment : Fragment(R.layout.fragment_home) {
private var logger = Logger.getLogger(HomeFragment::class.java.name)
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
val binding: FragmentHomeBinding = DataBindingUtil.bind(view)
val recyclerView: RecyclerView = binding.transactionList
val transactionAdapter = TransactionAdapter()
recyclerView.adapter = transactionAdapter
recyclerView.layoutManager = LinearLayoutManager(context)
transactionAdapter.data = listOf(
Transaction(10, "Transaction note", "Groceries"),
Transaction(20, "Transaction note", "Groceries"),
Transaction(30, "Transaction note", "Groceries")
)
}
}
My Recyclerview sometimes needs a refresh to start displaying data, some times it need much time to display the data and sometimes it works like a charm. I even used the same adapter for 2 recycler views one of them displayed data immediately but the other didn't show at all like the the image below:
This problem started to show up when I added another recycler view but in a different fragment that used same adapter. I thought using same adapter was the problem so I created a new one but the problem is still there. Note: I'm using same layout for displaying the recycler view items too. Any Ideas would be really helpful.
Thanks
UPDATE: Codes
Adapter :
import android.graphics.Paint
import android.view.LayoutInflater
import android.view.TextureView
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.ecommapp.R
import com.example.ecommapp.main_activity.data.Product
import kotlinx.android.synthetic.main.product_item_layout.view.*
class ProductItemAdapter(val listener: ProductItemAdapter.onItemClickListener) : ListAdapter<Product, ProductItemAdapter.ProductViewHolder>(Comparator()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val inflater = LayoutInflater.from(parent.context).inflate(R.layout.product_item_layout, parent, false)
return ProductViewHolder(inflater)
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
val currentItem = getItem(position)
if (currentItem != null) {
holder.bind(currentItem)
}
}
inner class ProductViewHolder(view : View): RecyclerView.ViewHolder(view){
init {
view.setOnClickListener{
val position = adapterPosition
val product = getItem(position)
if(position != RecyclerView.NO_POSITION){
listener.onItemClick(product)
}
}
}
private val container = view.container_main
private val imageView = view.img_product_item_not_card
private val productName = view.tv_product_item_label
private val productNewPrice = view.tv_product_item_new_price
private val productOldPrice = view.tv_product_item_old_price
fun bind(product: Product) {
productName.text = product.title
productNewPrice.text = product.price
/*var temp = (product.price as Double ) * 1.5
var oldPrice = temp as String
productOldPrice.text = oldPrice
productOldPrice.paintFlags = productOldPrice.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG*/
//Context of the view
Glide.with(imageView.context) // Context
.load(product.image) // Data
.into(imageView) // View
}
}
interface onItemClickListener{
fun onItemClick(product : Product)
}
class Comparator : DiffUtil.ItemCallback<Product>() {
override fun areItemsTheSame(oldItem: Product, newItem: Product) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Product, newItem: Product) =
oldItem == newItem
}
}
View Model:
import android.content.ContentValues.TAG
import android.util.Log
import androidx.lifecycle.*
import com.example.ecommapp.main_activity.data.Product
import com.example.ecommapp.main_activity.retrofit.ProductsApi
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.launch
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import javax.inject.Inject
#HiltViewModel
class HomeViewModel #Inject constructor(
private val savedStateHandle: SavedStateHandle,
val api : ProductsApi
) : ViewModel() {
var new_collection_list : MutableLiveData<List<Product>>
var best_sellling_list : MutableLiveData<List<Product>>
init {
new_collection_list = MutableLiveData()
best_sellling_list = MutableLiveData()
get_best_selling_data()
get_new_collection_data()
}
fun get_new_collection_data(){
var call = api.get_products_desc()
call.enqueue(object : Callback<List<Product>> {
override fun onResponse(call: Call<List<Product>>, response: Response<List<Product>>) {
if (response.body() != null){
new_collection_list.postValue(response.body())
Log.d(TAG, "onResponse: Success Response")
}
else{
new_collection_list.postValue(null)
Log.d(TAG, "onResponse: Null Response")
}
}
override fun onFailure(call: Call<List<Product>>, t: Throwable) {
Log.d(TAG, "onFailure: Failure Response")
}
})
}
fun get_best_selling_data() {
var call = api.get_products_asc()
call.enqueue(object : Callback<List<Product>> {
override fun onResponse(call: Call<List<Product>>, response: Response<List<Product>>) {
if (response.body() != null){
best_sellling_list.postValue(response.body())
Log.d(TAG, "onResponse: Success Response")
}
else{
best_sellling_list.postValue(null)
Log.d(TAG, "onResponse: Null Response")
}
}
override fun onFailure(call: Call<List<Product>>, t: Throwable) {
Log.d(TAG, "onFailure: Failure Response")
}
})
}
fun on_swipe_refresh(){
get_new_collection_data()
get_best_selling_data()
}
}
Fragment :
package com.example.ecommapp.main_activity.fragments.home_fragment
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.example.ecommapp.R
import com.example.ecommapp.main_activity.data.Product
import com.example.ecommapp.main_activity.shared_files.recycler_view_adapters.ProductItemAdapter
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.home_fragment_layout.*
#AndroidEntryPoint
class HomeFragment: Fragment(R.layout.home_fragment_layout), ProductItemAdapter.onItemClickListener {
lateinit var new_collection_list : LiveData<List<Product>>
lateinit var best_selling_list : LiveData<List<Product>>
val new_collections_adapter = ProductItemAdapter(this)
val best_selling_adapter = ProductItemAdapter(this)
val viewModel : HomeViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// New Collection Recycler View Setup
rv_new_collections.layoutManager = LinearLayoutManager(
activity?.applicationContext,
LinearLayoutManager.HORIZONTAL,
false
)
rv_new_collections.adapter = new_collections_adapter
// Best Selling Recycler View Setup
rv_best_selling.layoutManager = LinearLayoutManager(
activity?.applicationContext,
LinearLayoutManager.HORIZONTAL,
false
)
rv_best_selling.adapter = best_selling_adapter
//
set_data()
Handler().postDelayed({
if ( isConnected(activity?.applicationContext) ){
layout_new_collection_shimmer.visibility = View.INVISIBLE
rv_new_collections.visibility = View.VISIBLE
layout_best_selling_shimmer.visibility = View.INVISIBLE
rv_best_selling.visibility = View.VISIBLE
set_data()
}
else{
Toast.makeText(activity?.applicationContext, "No Internet Connection, Swipe to reload.", Toast.LENGTH_LONG )
.show()
}
}, 2000)
container_swipe.setOnRefreshListener(object : SwipeRefreshLayout.OnRefreshListener {
override fun onRefresh() {
if ( isConnected(activity?.applicationContext) ){
if( layout_new_collection_shimmer.visibility == View.INVISIBLE){
layout_new_collection_shimmer.visibility = View.INVISIBLE
rv_new_collections.visibility = View.VISIBLE
layout_best_selling_shimmer.visibility = View.INVISIBLE
rv_best_selling.visibility = View.VISIBLE
}
viewModel.on_swipe_refresh()
set_data()
}
else{
Toast.makeText(activity?.applicationContext, "No Internet Connection, Swipe to reload.", Toast.LENGTH_LONG )
.show()
}
// Must be added
container_swipe.isRefreshing = false
}
})
}
fun set_data(){
new_collection_list = viewModel.new_collection_list
best_selling_list = viewModel.best_sellling_list
new_collection_list.observe(viewLifecycleOwner, Observer {
new_collections_adapter.submitList(it)
new_collections_adapter.notifyDataSetChanged()
})
best_selling_list.observe(viewLifecycleOwner, Observer {
best_selling_adapter.submitList(it)
best_selling_adapter.notifyDataSetChanged()
})
}
fun isConnected(ctx: Context?): Boolean {
val hasInternet: Boolean
val connectivityManager =
ctx?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = connectivityManager.activeNetwork ?: return false
val actNw =
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
hasInternet = when {
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
hasInternet = try {
if (connectivityManager.activeNetworkInfo == null) {
false
} else {
connectivityManager.activeNetworkInfo?.isConnected!!
}
} catch (e: Exception) {
false
}
}
return hasInternet}
override fun onItemClick(product : Product) {
val action = HomeFragmentDirections.actionHomeFragmentToProductFragment(product, product.title, R.id.homeFragment)
findNavController().navigate(action)
}
}
Layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/container_swipe"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".main_activity.fragments.home_fragment.HomeFragment"
>
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.SearchView
android:id="#+id/search_view"
android:layout_width="0dp"
android:layout_height="50dp"
app:layout_constraintWidth_percent="0.9"
android:elevation="2dp"
android:outlineProvider="bounds"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="20dp"/>
<androidx.cardview.widget.CardView
android:id="#+id/img_advertisment"
android:layout_width="0dp"
android:layout_height="180dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/search_view"
app:layout_constraintWidth_percent="0.9"
android:background="#00000000"
app:cardElevation="0dp"
app:cardCornerRadius="10dp"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/advertisment"
/>
</androidx.cardview.widget.CardView>
<RelativeLayout
android:id="#+id/container_new_collection"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/img_advertisment"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintWidth_percent="0.9"
android:layout_marginTop="20dp"
>
<TextView
android:id="#+id/tv_new_collection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Collections"
android:textColor="#color/tomato"
android:textSize="25dp"
android:fontFamily="sans-serif-black"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show All"
android:textSize="20dp"
android:textColor="#c6c4ce"
android:fontFamily="sans-serif-black"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginTop="5dp"/>
<com.facebook.shimmer.ShimmerFrameLayout
android:id="#+id/layout_new_collection_shimmer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:shimmer_repeat_mode="restart"
app:shimmer_shape="linear"
android:layout_below="#id/tv_new_collection"
android:layout_marginTop="10dp">
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
</LinearLayout>
</HorizontalScrollView>
</com.facebook.shimmer.ShimmerFrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:layout_marginTop="10dp"
android:id="#+id/rv_new_collections"
android:layout_width="wrap_content"
android:layout_height="350dp"
android:layout_below="#id/tv_new_collection"
tools:listitem="#layout/product_item_layout"
android:visibility="invisible"
/>
</RelativeLayout>
<RelativeLayout
android:id="#+id/container_best_selling"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/container_new_collection"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintWidth_percent="0.9"
android:layout_marginTop="20dp"
>
<TextView
android:id="#+id/tv_best_selling"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Best Selling"
android:textColor="#color/tomato"
android:textSize="25dp"
android:fontFamily="sans-serif-black"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show All"
android:textSize="20dp"
android:textColor="#c6c4ce"
android:fontFamily="sans-serif-black"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginTop="5dp"
/>
<com.facebook.shimmer.ShimmerFrameLayout
android:id="#+id/layout_best_selling_shimmer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:shimmer_repeat_mode="restart"
app:shimmer_shape="linear"
android:layout_marginTop="10dp"
android:layout_below="#id/tv_best_selling">
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
<include layout="#layout/product_shimmer_layout"></include>
</LinearLayout>
</HorizontalScrollView>
</com.facebook.shimmer.ShimmerFrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_best_selling"
android:layout_width="wrap_content"
android:layout_height="350dp"
android:layout_below="#id/tv_best_selling"
tools:listitem="#layout/product_item_layout"
android:layout_marginTop="10dp"
android:visibility="invisible"
/>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
I Figured out what is the problem. The API takes much time to send the data. So, the recycler view renders the view with null values.
I am trying to implement onEditPost and onDeletePost inside of my RecyclerViewFragment:
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.MainActivity
import com.example.projectdrivemark.R
import com.example.projectdrivemark.databinding.FragmentListBinding
import com.example.projectdrivemark.databinding.PostBinding
class RecyclerViewFragment: Fragment(R.layout.fragment_list), PostAdapter.OnPostClickListener {
private lateinit var binding: FragmentListBinding
val dummyList = MockDatabase.createMockData()
val adapter = PostAdapter(dummyList, this)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreate(savedInstanceState)
binding = FragmentListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<RecyclerView>(R.id.recycleViewMain).apply{
layoutManager = LinearLayoutManager(requireActivity())
adapter = PostAdapter(dummyList, this#RecyclerViewFragment)
}
}
override fun onEditPost(position: Int){
val clickedPost = dummyList[position]
clickedPost.title = "Updated title"
clickedPost.body = "Updated body"
adapter.notifyItemChanged(position)
}
override fun onDeletePost(position: Int) {
dummyList.removeAt(position)
adapter.notifyItemRemoved(position)
}
}
Here is how my Adapter looks like:
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.MainActivity
import com.example.projectdrivemark.R
class PostAdapter(val dummyData: ArrayList<Post>, val myListener: OnPostClickListener) : RecyclerView.Adapter<PostAdapter.PostViewHolder>() {
inner class PostViewHolder(postView: View) : RecyclerView.ViewHolder(postView), View.OnClickListener{
val iconImage: ImageView = postView.findViewById(R.id.icon_image_view)
val title: TextView = postView.findViewById(R.id.title)
val body: TextView = postView.findViewById(R.id.body)
val deleteIcon: ImageView = postView.findViewById(R.id.delete_post_image)
val editIcon: ImageView = postView.findViewById(R.id.edit_post_image)
init {
deleteIcon.setOnClickListener(this)
editIcon.setOnClickListener(this)
}
override fun onClick(v: View?){
val position = adapterPosition
if(v?.id == editIcon.id){
myListener.onEditPost(position)
}else{
myListener.onDeletePost(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
val postView = LayoutInflater.from(parent.context).inflate(R.layout.post, parent, false)
return PostViewHolder(postView)
}
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
val currentPost = dummyData[position]
holder.iconImage.setImageResource(currentPost.image)
holder.title.text = currentPost.title
holder.body.text = currentPost.body
}
override fun getItemCount(): Int {
return dummyData.size
}
interface OnPostClickListener{
fun onEditPost(position: Int)
fun onDeletePost(position: Int)
}
}
And here is how my XML File Post looks like:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
android:layout_marginBottom="8dp"
android:padding="5dp">
<ImageView
android:id="#+id/icon_image_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:src="#drawable/ic_baseline_ac_unit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
android:text="Title of blog post"
android:textColor="#android:color/black"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="#+id/textView2"
app:layout_constraintStart_toEndOf="#+id/icon_image_view"
app:layout_constraintTop_toTopOf="#+id/icon_image_view" />
<TextView
android:id="#+id/body"
android:layout_width="285dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:text="Body of first blog post"
app:layout_constraintBottom_toBottomOf="#+id/icon_image_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.050"
app:layout_constraintStart_toEndOf="#+id/icon_image_view" />
<LinearLayout
android:id="#+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="end">
<ImageView
android:id="#+id/edit_post_image"
android:layout_width="60dp"
android:layout_height="50dp"
android:src="#drawable/ic_baseline_edit" />
<ImageView
android:id="#+id/delete_post_image"
android:layout_width="60dp"
android:layout_height="50dp"
android:src="#drawable/ic_baseline_delete_forever" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
As you see in the RecyclerViewFragment, I have 2 functions that is onEditPost and onDeletePost. I want that functionality to work when I press it. Right now, it only works when I press and change the fragment view. I want it to work instantly. If anyone see my mistake or Am I missing something? please do tell me. All help is appreciate
You are creating Adapter twice, you already have an Adapter initialised globally use it in onViewCreated instead of assign again. because you are using global adapter to notify changes.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<RecyclerView>(R.id.recycleViewMain).apply{
layoutManager = LinearLayoutManager(requireActivity())
adapter = this#RecyclerViewFragment.adapter
}
}
I created a RecyclerView layout, but the look does not match what I expected
I have checked layout_height on list_item.xml, but already wrap_content.
But why it looks does not fit.
And TextView with id tv_title_list not appear when executed
Display Screenshots : https://drive.google.com/open?id=1hywe6DrXth6iwO9jIgScAxhTq1Cq3mAL
For full code : https://github.com/riluq/AnimeApp
list_item.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="topAiring" type="com.riluq.animeapp.network.TopAiring"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="#+id/mcv_list"
android:layout_width="65dp"
android:layout_height="90dp"
app:cardCornerRadius="5dp"
app:cardElevation="2dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginBottom="16dp" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="8dp"
android:layout_marginTop="16dp">
<ImageView
android:id="#+id/img_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:srcCompat="#android:color/holo_blue_light"
app:imageUrl="#{topAiring.imageUrlTopAiring}"/>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="#+id/tv_title_list"
tools:text="JoJo no Kimyou na Bouken Part 5: Ougon no Kaze"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAppearance="#style/TextAppearance.MaterialComponents.Subtitle2"
app:layout_constraintStart_toEndOf="#+id/mcv_list"
android:layout_marginStart="8dp" app:layout_constraintTop_toTopOf="#+id/mcv_list"
app:layout_constraintBottom_toBottomOf="#+id/mcv_list"
android:layout_marginEnd="8dp" app:layout_constraintEnd_toStartOf="#+id/tv_rank_list"
app:textTopAiringTitle="#{topAiring.titleTopAiring}"/>
<TextView
android:id="#+id/tv_rank_list"
tools:text="#1"
android:textColor="?attr/colorSecondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="#style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp"
app:layout_constraintTop_toTopOf="#+id/mcv_list"
app:textTopAiringRank="#{topAiring.rankTopAiring}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
fragment_top_airing.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="viewModel"
type="com.riluq.animeapp.topairing.TopAiringViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".topairing.TopAiringFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_top_airing"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:listData="#{viewModel.topAiring}"
tools:itemCount="16"
tools:listitem="#layout/list_item"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
TopAiringAdapter.kt
package com.riluq.animeapp.topairing
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.riluq.animeapp.databinding.ListItemBinding
import com.riluq.animeapp.network.TopAiring
class TopAiringAdapter(): ListAdapter<TopAiring, TopAiringAdapter.TopAiringViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TopAiringViewHolder {
return TopAiringViewHolder(ListItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: TopAiringViewHolder, position: Int) {
val topAiring = getItem(position)
holder.bind(topAiring)
}
class TopAiringViewHolder(private val binding: ListItemBinding) :
RecyclerView.ViewHolder(binding.root){
fun bind(topAiring: TopAiring) {
binding.topAiring = topAiring
binding.executePendingBindings()
}
}
companion object DiffCallback: DiffUtil.ItemCallback<TopAiring>() {
override fun areItemsTheSame(oldItem: TopAiring, newItem: TopAiring): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: TopAiring, newItem: TopAiring): Boolean {
return oldItem.idTopAiring == newItem.idTopAiring
}
}
}
TopAiringFragment.kt
package com.riluq.animeapp.topairing
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import com.riluq.animeapp.databinding.FragmentTopAiringBinding
class TopAiringFragment : Fragment() {
private val viewModel: TopAiringViewModel by lazy {
ViewModelProviders.of(this).get(TopAiringViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentTopAiringBinding.inflate(inflater)
binding.lifecycleOwner = this
// Inflate the layout for this fragment
binding.viewModel = viewModel
binding.rvTopAiring.adapter = TopAiringAdapter()
return binding.root
}
}
TopAiringViewModel.kt
package com.riluq.animeapp.topairing
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.riluq.animeapp.network.JikanMoeApi
import com.riluq.animeapp.network.TopAiring
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class TopAiringViewModel: ViewModel() {
private var viewModelJob = Job()
private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)
private val _topAiring = MutableLiveData<List<TopAiring>>()
val topAiring: LiveData<List<TopAiring>>
get() = _topAiring
init {
getTopAiring()
}
private fun getTopAiring() {
coroutineScope.launch {
val getTopAiringDeffered = JikanMoeApi.retrofitService.getTopAiringAsync()
try {
val listResult = getTopAiringDeffered.await().top
if (listResult.size > 0) {
_topAiring.value = listResult
}
Log.i("TopAiringViewModel", "topAiring = ${topAiring.value?.get(0)?.titleTopAiring}")
} catch (t: Throwable) {
_topAiring.value = ArrayList()
Log.i("TopAiringViewModel", t.message.toString())
}
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
What I expected is the look like this picture : https://drive.google.com/open?id=1d1sWL0CX7gPJFvvhY13POWDAf15Hk2Pw
Add this property in recyclerview for left, right, top and bottom:
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
...like wise for top and bottom