Currently, I am trying to implement RecyclerView inside of Fragment but I cannot find a way to display it.
Here is my MainActivity:
package com.example
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import com.example.projectdrivemark.R
import com.example.projectdrivemark.databinding.ActivityMainBinding
import com.example.recyclerView.MockDatabase.Companion.createMockData
import com.example.recyclerView.PostAdapter
import com.example.recyclerView.RecyclerViewFragment
import com.example.tempConverter.TempConverterFragment
import com.example.uploaderView.UploaderFragment
class MainActivity : AppCompatActivity(), PostAdapter.OnPostClickListener {
private lateinit var binding: ActivityMainBinding
val dummyList = createMockData()
val adapter = PostAdapter(dummyList, this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
title = "First Kotlin App"
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val recyclerView = RecyclerViewFragment()
val tempConverterView = TempConverterFragment()
// recyclerView.layoutManager = LinearLayoutManager(this)
val uploaderView = UploaderFragment(this)
setFragmentView(recyclerView)
binding.bottomNavBar.setOnNavigationItemSelectedListener {
when(it.itemId){
R.id.listView -> setFragmentView(recyclerView)
R.id.tempConverterView -> setFragmentView(tempConverterView)
R.id.videoUploaderView -> setFragmentView(uploaderView)
}
true
}
}
private fun setFragmentView(fragment: Fragment){
supportFragmentManager.beginTransaction().apply {
replace(R.id.main_fragment_view, fragment)
//Will return to previous page when tap "Back Button" on the phone
addToBackStack(null)
commit()
}
}
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)
}
fun celsiusFunction(view: View) {
val tempConverter = TempConverterFragment()
tempConverter.celsiusFunction(view)
}
fun farenheitFunction(view: View){
val fahrenheitConverter = TempConverterFragment()
fahrenheitConverter.farenheitFunction(view)
}
}
Here is my RecyclerFragment:
package com.example.recyclerView
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.MainActivity
import com.example.projectdrivemark.R
class RecyclerViewFragment: Fragment() {
var adapter: RecyclerView.Adapter<PostAdapter.PostViewHolder>? = null
var layoutManager: RecyclerView.LayoutManager? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreate(savedInstanceState)
val binding = inflater.inflate(R.layout.fragment_list, container, false)
return binding
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.apply{
layoutManager = LinearLayoutManager(activity)
adapter = PostAdapter(dummyData = ArrayList<Post>(), MainActivity())
}
}
}
Here is my PostAdapter code:
package com.example.recyclerView
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: MainActivity) : 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)
}
}
If anyone saw something I miss, please do tell me because I am stuck at how to display the RecyclerView. Any help would be appreciate.
Edit:
Here is my MockDatabase:
package com.example.recyclerView
import com.example.projectdrivemark.R
class MockDatabase {
companion object{
fun createMockData(): ArrayList<Post>{
val list = ArrayList<Post>()
for(i in 0 until 20){
val imageToSelect = when (i % 3){
0 -> R.drawable.ic_baseline_account_balance
1 -> R.drawable.ic_baseline_account_circle
2 ->R.drawable.ic_baseline_ac_unit
else -> R.drawable.ic_baseline_access_alarms
}
list.add(
Post(
imageToSelect,
title = "Title post of $i",
body = "Title post of $i"
)
)
}
return list
}
}
}
Here is my Layout_list xml file:
<?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="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycleViewMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
tools:listitem="#layout/recycler_view_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
You need to cast view to RecyclerView to access RecyclerView properties inside apply, also you cannot create instances of Activity by yourself,
MainActivity() is wrong and won't work, instead use requireActivity() which will provide context of Activity to which Fragment is attached to
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewbyId<RecyclerView>(R.id.recycleViewMain).apply{
layoutManager = LinearLayoutManager(requireActivity())
adapter = PostAdapter(dummyData = ArrayList<Post>(), requireActivity() as OnPostClickListener)
}
}
Also, as #Necronet mentioned, add some mock data to see if it is actually rendering.
UPDATE
Instead of passing MainActivity to Adapter, pass OnPostClickListener
adapter = PostAdapter(dummyData = ArrayList<Post>(), requireActivity() as OnPostClickListener)
Adapter
class PostAdapter(val dummyData: ArrayList<Post>, val myListener: OnPostClickListener) : RecyclerView.Adapter<PostAdapter.PostViewHolder>(){
// you can use myListener to call methods
}
It's simple you are passing empty dummyData try using createMockData() instead. Also as a rule of thumb in Android, never ever instatiate an Activity yourself, if you need the reference from within a fragment you can use getActivity() method. So this:
adapter = PostAdapter(dummyData = ArrayList<Post>(), MainActivity())
Should be:
adapter = PostAdapter(dummyData = createMockData(), getActivity() as MainActivity)
Related
I have fragment which showing weather for 10 days with getting geolocation city and show exactly weather for this city for 10 days.
I have problem initialization with RecyclerView and Viewmodel.
Also I use Hilt to provide dependencies.
My goal is showing weather by location (already have permission) for 10 days.
import androidx.lifecycle.LiveData
import dagger.hilt.android.lifecycle.HiltViewModel
import db.entities.WeatherData
#HiltViewModel
interface ForecastViewmodel {
val dataforecast:LiveData<List<WeatherData>>
val city : LiveData<String>
val isRefreshing:LiveData<Boolean>
fun onRefresh()
}
#HiltViewModel
class ForecastViewmodelImpl(private val repository: ForecastRepository,
private val day: Day
) : ForecastViewmodel,
ViewModel(){
override val dataforecast = MutableLiveData<List<WeatherData>>()
override val city = MutableLiveData(day.city)
override val isRefreshing = MutableLiveData(true)
init {
loadForecast()
}
private fun loadForecast() {
isRefreshing.value = true
viewModelScope.launch {
try {
val data = repository.getForecast(day.days)
Timber.d("size is ${data}")
}catch (e:Exception){
Timber.e(e)
}
isRefreshing.value = false
}
}
override fun onRefresh() = loadForecast()
private fun Forecastday.toWeatherData() = WeatherData(
date = date,
temp = "${temp.roundToInt()}℃"
)
}
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.testtaskweatherappkevychsol.R
import db.entities.WeatherData
class WeatherRecView:RecyclerView.Adapter<WeatherRecView.WeatherHolder>() {
var listweather = emptyList<WeatherData>()
class WeatherHolder(view:View):RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): WeatherHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.daily_weather_layout,
parent,false)
return WeatherHolder(view)
}
override fun onBindViewHolder(
holder: WeatherHolder,
position: Int
) {
holder.itemView.findViewById<TextView>(R.id.Dateitem).text = listweather[position].date
holder.itemView.findViewById<TextView>(R.id.tempitem).text = listweather[position]
.temp.toString()
}
override fun getItemCount(): Int {
return listweather.size
}
fun addlistintoUI(list: List<WeatherData>){
listweather = list
}
}
data class WeatherData(
val temp:String,
val date:String
)
import RecyclerView.WeatherRecView
import Viewmodel.ForecastViewmodel
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.testtaskweatherappkevychsol.R
import com.example.testtaskweatherappkevychsol.databinding.FragmentWeatherBinding
import dagger.hilt.android.AndroidEntryPoint
#AndroidEntryPoint
class WeatherAtTheLifeMomentFragment : Fragment(R.layout.fragment_weather){
lateinit var recview:WeatherRecView
private var binding : FragmentWeatherBinding? = null
lateinit var viewmodel:ForecastViewmodel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentWeatherBinding.inflate(inflater,container,false)
this.binding = binding
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding){
this!!.refreshbt.setOnClickListener { viewmodel.isRefreshing }
containerlistweather.adapter = recview
containerlistweather.addItemDecoration(DividerItemDecoration(containerlistweather.context,
(containerlistweather
.layoutManager as LinearLayoutManager).orientation))
with(viewmodel){
dataforecast.observe(viewLifecycleOwner) { recview.listweather = it }
City.text = city.toString()
}
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}
lateinit property recview has not been initialized
you are getting this issue as you are trying to access lateinit property recview without initialising it
in WeatherAtTheLifeMomentFragment
From
containerlistweather.adapter = recview
To
recview = WeatherRecView() // initialise recview variable by creating instance of your adapter
containerlistweather.layoutManager = LinearLayoutManager(context) // not sure you have set layout manager in your xml so just adding it in your code as it is necessary to use
containerlistweather.adapter = recview
Also you have create method addlistintoUI in WeatherRecView class so use it and make below mentioned changes to load your data
in WeatherAtTheLifeMomentFragment class
From
dataforecast.observe(viewLifecycleOwner) { recview.listweather = it }
To
dataforecast.observe(viewLifecycleOwner) { recview.addlistintoUI(it) }
in WeatherRecView class
From
fun addlistintoUI(list: List<WeatherData>){
listweather = list
}
To
fun addlistintoUI(list: List<WeatherData>){
listweather.addAll(list)
notifyDataSetChanged()
}
```
I would like to navigate between fragments using a textView inside a recyclerview.
Currently I am successfully navigating between fragments using the item, but I would like to go further and navigate using the individual textViews within an item.
The recyclerview is inflated from a local room database.
Below is my adapter
package com.benb.inventory
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.benb.inventory.data.Item
import com.benb.inventory.data.getFormattedPrice
import com.benb.inventory.databinding.ItemListItemBinding
class ItemListAdapter(private val onItemClicked: (Item) -> Unit) :
ListAdapter<Item, ItemListAdapter.ItemViewHolder>(DiffCallback) {
class ItemViewHolder(internal var binding: ItemListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
binding.apply {
itemName.text = item.itemName
itemPrice.text = item.getFormattedPrice()
itemShop.text = item.shop.toString()
itemShop.setOnClickListener{
val shopName = itemShop.text.toString()
Toast.makeText(root.context, "Clicked: ${item.shop}", Toast.LENGTH_SHORT ).show()
}
}
}
}
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.itemName == newItem.itemName
}
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ItemListAdapter.ItemViewHolder {
return ItemViewHolder(ItemListItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: ItemListAdapter.ItemViewHolder, position: Int) {
val current = getItem(position)
holder.itemView.setOnClickListener {
onItemClicked(current)
}
holder.binding.itemShop.setOnClickListener {
onItemClicked(current)
val shopName = current.shop
fun showShopList(shopName: String) {
val shopName = holder.binding.itemShop.text.toString()
val action = ItemListFragmentDirections.actionItemListFragmentToShopItemFragment(shopName)
}
}
holder.bind(current)
}
}
This is the fragment that contains the list
package com.benb.inventory
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.benb.inventory.databinding.ItemListFragmentBinding
import kotlinx.coroutines.InternalCoroutinesApi
#InternalCoroutinesApi
class ItemListFragment : Fragment() {
#InternalCoroutinesApi
private val viewModel: InventoryViewModel by activityViewModels {
InventoryViewModelFactory(
(activity?.application as InventoryApplication).database.itemDao()
)
}
private var _binding: ItemListFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ItemListFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = ItemListAdapter {
val action = ItemListFragmentDirections.actionItemListFragmentToItemDetailFragment(it.id)
this.findNavController().navigate(action)
}
binding.recyclerView.adapter = adapter
viewModel.allItems.observe(this.viewLifecycleOwner) {items ->
items.let {
adapter.submitList(it)
}
}
binding.itemShop.setOnClickListener{
val shop = binding.itemShop.text.toString()
val action = ItemListFragmentDirections.actionItemListFragmentToShopItemFragment(shop)
viewModel.retrieveShopItems(shop)
this.findNavController().navigate(action)
}
binding.recyclerView.layoutManager = LinearLayoutManager(this.context)
binding.floatingActionButton.setOnClickListener {
val action = ItemListFragmentDirections.actionItemListFragmentToAddItemFragment(
getString(R.string.add_fragment_title)
)
this.findNavController().navigate(action)
}
}
}
For clarity this is what the inflated recyclerview looks like, it can contain many items, I have only added one.
At the moment if I click anywhere on the item it takes you to a screen with more detail about the fragment.
[A screenshot of the recycler view][1]
I would like to be able to navigate to a specific fragment depending on the textView clicked.
For example one fragment that only contains other products from the same shop.
As you may notice, I have been able to add an onClickListener in the ViewHolder class, it creates a Toast.
I have not had success in using the same onClickListener to navigate.
Thank you very much.
P.S. Any other advice is welcome, particularly if anyone knows what the #internalcoroutinesAPI thing is about please tell me!
[1]: https://i.stack.imgur.com/k6Td3.png
I'm using Kotlin for my app.
I have a problem I can't resolve.
I'm using a RecyclerView inside a fragment that show a Grid with 2 items per row.
Inside each item there are text and image loaded from Firebase.
Everything works perfectly. But, I don't know why, randomly, the images disapear and return a white element. The text doesn't changes, only the image changes.
There is two screens, just few seconds appart. In the first one everything is ok, it shows 4 éléments. Then I restart the app and 3 of the 4 images disapear (second screen). Only the last load to and from Firebase stays.
Inside the code, I'm using a data class named DataClassArticle, an adapter named ArticleAdapter and the fragment named Flux.
1 - First screen
2 - Second sceen
3 - Firebase
4 - Data Class
5 - Adapter
6 - Fragment
1 - FIRST SCREEN (EVERYTHING WORK) :
2- SECOND SCREEN (3 OF THE 4 IMAGES DISAPEAR) :
3 - FIREBASE (RETURNING : DESCRIPTION, PRIXVENTE, TITREVENTE, ZONE, PHOTO)
4- DATA CLASS
data class DataClassArticle(
var titrevente: String? = null,
var prixvente: String? = null,
var zone: String? = null,
var photo: String? = null
)
5- ADAPTER
import android.util.Log
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.givenaskv1.R
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import com.squareup.picasso.Picasso
class ArticleAdapter(private var articleList: ArrayList<DataClassArticle>) : RecyclerView.Adapter<ArticleAdapter.ViewHolder>() {
val TAG = "TAG"
var dataList = emptyList<DataClassArticle>()
internal fun setDataList(dataList : List<DataClassArticle>) {
this.dataList = dataList
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var articleImage : ImageView
var articleTitle : TextView
var articlePrice : TextView
var articleLocalisation : TextView
init {
articleImage = itemView.findViewById(R.id.articleImage)
articleTitle = itemView.findViewById(R.id.titreArticle)
articlePrice = itemView.findViewById(R.id.prixArticle)
articleLocalisation = itemView.findViewById(R.id.nomVilleArticle)
val myId = FirebaseAuth.getInstance().uid
com.example.givenaskv1.Map.dbref = FirebaseDatabase.getInstance().getReference("Vente")
com.example.givenaskv1.Map.dbref.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
for (missionSnapshot in snapshot.children) {
// if (missionSnapshot.child("uid").value.toString() == myId) {
val test = missionSnapshot.child("uid").value.toString()
Log.d(TAG, "Ceci est l'UID : ${test}.")
// }
}
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.post_vente, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ArticleAdapter.ViewHolder, position: Int) {
val data = articleList[position]
holder.articleTitle.text = data.titrevente
holder.articlePrice.text = data.prixvente
holder.articleLocalisation.text = data.zone
Picasso.get().load(data.photo).into(holder.articleImage)
}
override fun getItemCount() : Int {
return articleList.size
}
}
6- FRAGMENT
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.GenericTransitionOptions.with
import com.bumptech.glide.Glide.with
import com.bumptech.glide.load.resource.bitmap.BitmapTransitionOptions.with
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.with
import com.example.givenaskv1.R
import com.google.firebase.database.*
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.fragment_flux.*
import kotlinx.android.synthetic.main.fragment_notification.*
import kotlinx.android.synthetic.main.post_vente.*
private const val TAG = "TAG"
class Flux : Fragment() {
private var layoutManager: RecyclerView.LayoutManager? = null
private var adapter: RecyclerView.Adapter<ArticleAdapter.ViewHolder>? = null
private lateinit var dbref : DatabaseReference
private lateinit var userRecyclerview : RecyclerView
private lateinit var userArrayList : ArrayList<DataClassArticle>
private lateinit var articleAdapter: ArticleAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_flux, container, false)
}
override fun onViewCreated(itemView: View, savedInstanceState: Bundle?) {
super.onViewCreated(itemView, savedInstanceState)
// RvPosts = le recyclerview
btnPublierVente.setOnClickListener {
val bottomSheet = BottomsheetVentes()
bottomSheet.show(fragmentManager!!, "BottomSheet")
}
rvPosts.apply {
userRecyclerview = findViewById(R.id.rvPosts)
userRecyclerview.setHasFixedSize(true)
userArrayList = arrayListOf<DataClassArticle>()
layoutManager = LinearLayoutManager(activity)
rvPosts.setLayoutManager(GridLayoutManager(this.context, 2))
getUserData()
}
}
private fun getUserData() {
dbref = FirebaseDatabase.getInstance().getReference("Vente")
dbref.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (userSnapshot in snapshot.children){
val user = userSnapshot.getValue(DataClassArticle::class.java)
userArrayList.add(user!!)
userRecyclerview.adapter = ArticleAdapter(userArrayList)
}
}
override fun onCancelled(error: DatabaseError) {
TODO("Not yet implemented")
}
})
}
}
Thank you guys for your help.
Have a good day !
My app is a dictionary app. I am using a room database to save the recent queries in my app. I am showing all the contents of the room database inside my recycler view. When the user searches for a word, the user is taken to the result fragment and the query is added to the room database behind the scenes. It also plays a slide in from right animation when the new fragment is getting added. On pressing the back or the up button, it should take you back with an animation the top level fragment, the main fragment. But the problem I am facing is that something with the recycler view makes that animation look very choppy.
This is how it should look: https://i.imgur.com/vdOtA2Y.mp4
This is how it looks: https://i.imgur.com/OKpDj7G.mp4
I am pasting the fragment with the recycler view, viewmodel and the recycler view adapter here and I am also pasting a link to the whole app's github repo https://github.com/sbeve72/JADA
package com.sbeve.jada.fragments.main
import android.os.Bundle
import android.view.View
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.sbeve.jada.R
import com.sbeve.jada.activities.MainActivity
import com.sbeve.jada.databinding.FragmentMainBinding
import com.sbeve.jada.recyclerview_utils.RecentQueriesAdapter
import com.sbeve.jada.retrofit_utils.RetrofitInit
class MainFragment : Fragment(R.layout.fragment_main), RecentQueriesAdapter.OnItemClickListener {
private val navController: NavController by lazy {
this.findNavController()
}
//the currently running instance of the activity
private val mainActivityContext: MainActivity by lazy {
activity as MainActivity
}
private val stayInPlaceAnimation: Animation? by lazy {
val anim: Animation = AlphaAnimation(1.0F, 1.0F)
anim.duration = 150
anim
}
private val viewModel: MainViewModel by viewModels()
private lateinit var fragmentMainBinding: FragmentMainBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fragmentMainBinding = FragmentMainBinding.bind(view)
fragmentMainBinding.queriesRecyclerView.layoutManager = LinearLayoutManager(mainActivityContext)
fragmentMainBinding.currentLanguage.text = RetrofitInit.supportedLanguages.first[mainActivityContext.savedLanguageIndex]
fragmentMainBinding.changeLanguageGearIcon.setOnClickListener {
createChangeLanguageDialog().show()
}
fragmentMainBinding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
//work
viewModel.addQuery(mainActivityContext.savedLanguageIndex, query)
navController.navigate(MainFragmentDirections.actionMainFragmentToResultFragment(query))
hideSoftKeyboard()
return true
}
override fun onQueryTextChange(newText: String?) = false
})
setAdapter()
}
override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int) = stayInPlaceAnimation
private fun createChangeLanguageDialog() =
MaterialAlertDialogBuilder(mainActivityContext)
.setTitle(getString(R.string.choose_a_language))
.setSingleChoiceItems(RetrofitInit.supportedLanguages.first, mainActivityContext.savedLanguageIndex)
{ dialogInterface, i ->
fragmentMainBinding.currentLanguage.text = RetrofitInit.supportedLanguages.first[i]
mainActivityContext.applicationSharedPreferences
.edit()
.putInt(getString(R.string.language_setting_key), i)
.apply()
dialogInterface.dismiss()
}
.create()
//hides the keyboard
private fun hideSoftKeyboard() {
val imm: InputMethodManager = mainActivityContext.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
//Find the currently focused view, so we can grab the correct window token from it.
var view = mainActivityContext.currentFocus
//If no view currently has focus, create a new one, just so we can grab a window token from it
if (view == null) {
view = View(activity)
}
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
private fun setAdapter() {
viewModel.allQueries.observe(viewLifecycleOwner) {
val adapter = RecentQueriesAdapter(it, this)
fragmentMainBinding.queriesRecyclerView.adapter = adapter
}
}
override fun onItemClick(position: Int) {
val queryText = viewModel.allQueries.value!![position].queryText
navController.navigate(MainFragmentDirections.actionMainFragmentToResultFragment(queryText))
}
}
package com.sbeve.jada.fragments.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sbeve.jada.myApplication
import com.sbeve.jada.retrofit_utils.RetrofitInit
import com.sbeve.jada.room_utils.DictionaryDatabase
import com.sbeve.jada.room_utils.DictionaryDatabaseDAO
import com.sbeve.jada.room_utils.RecentQuery
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainViewModel : ViewModel() {
private var roomDatabase = DictionaryDatabase.getInstance(myApplication.getInstance())
private val databaseDao: DictionaryDatabaseDAO = roomDatabase.getDao()
val allQueries = databaseDao.getAllQueries()
fun addQuery(languageIndex: Int, query: String) {
viewModelScope.launch {
withContext(IO) {
val recentQuery = RecentQuery(0, query, RetrofitInit.supportedLanguages.first[languageIndex], System.currentTimeMillis())
databaseDao.addQuery(recentQuery)
}
}
}
}
package com.sbeve.jada.recyclerview_utils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.text.HtmlCompat
import androidx.recyclerview.widget.RecyclerView
import com.sbeve.jada.databinding.QueryLayoutBinding
import com.sbeve.jada.room_utils.RecentQuery
import java.text.SimpleDateFormat
import java.util.*
class RecentQueriesAdapter(private val dataSet: List<RecentQuery>, private val onItemClickListener: OnItemClickListener) :
RecyclerView.Adapter<RecentQueriesAdapter.ViewHolder>() {
class ViewHolder(myItemView: QueryLayoutBinding, private val onItemClickListener: OnItemClickListener) :
RecyclerView.ViewHolder(myItemView.root), View.OnClickListener {
init {
myItemView.root.setOnClickListener(this)
}
private val queryText = myItemView.queryText
fun setQueryText(queryValue: String) {
queryText.text = HtmlCompat.fromHtml(queryValue, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
private val time = myItemView.time
fun setTimeText(timeValue: Long) {
time.text = SimpleDateFormat.getDateTimeInstance().format(Date(timeValue))
}
override fun onClick(v: View?) {
onItemClickListener.onItemClick(adapterPosition)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = QueryLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding, onItemClickListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = dataSet[position]
holder.setQueryText(currentItem.queryText)
holder.setTimeText(currentItem.timeDate)
}
override fun getItemCount() = dataSet.size
interface OnItemClickListener {
fun onItemClick(position: Int)
}
}
I have fetched response using retrofit library which I double checked before adding recyclerView and it works fine but after adding recyclerView and adapter, the data is not getting displayed.
ApiService.kt
package com.kunalrai.githubtrends
import com.squareup.moshi.Moshi
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.http.GET
private const val BASE_URL = "https://github-trending-api.now.sh"
val moshi: Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface ApiService {
#GET("repositories")
fun getRepos(): Call<List<Repo>>
}
object Api {
val RETROFIT_SERVICE : ApiService by lazy { retrofit.create(ApiService::class.java) }
}
ListAdapter.kt
package com.kunalrai.githubtrends
import android.content.Context
import android.util.Log
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.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
class ListAdapter(private val context: Context?, private val repoList: List<Repo>) : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.repo_item,parent,false)
return MyViewHolder(view)
}
override fun getItemCount(): Int {
Log.i("reposize: ",""+repoList.size)
return repoList.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.author.text = repoList[position].author
holder.repo.text = repoList[position].name
Glide.with(context!!).load(repoList[position].avatar)
.apply(RequestOptions().centerCrop())
.into(holder.image)
}
class MyViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView!!) {
val author: TextView = itemView!!.findViewById(R.id.owner_name)
val image: ImageView = itemView!!.findViewById(R.id.owner_image)
val repo: TextView = itemView!!.findViewById(R.id.repo_name)
}
}
ListViewModel.kt
package com.kunalrai.githubtrends
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class ListViewModel : ViewModel() {
var repoList: MutableLiveData<List<Repo>> = MutableLiveData(listOf())
fun getRepos(): MutableLiveData<List<Repo>>{
repoList = MutableLiveData()
loadRepos()
return repoList
}
private fun loadRepos() {
Api.RETROFIT_SERVICE.getRepos().enqueue( object: Callback<List<Repo>> {
override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
Log.i("Failure: ", t.message)
}
override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
if(response.body() != null){
repoList.value = response.body()
Log.i("response.body :",""+response.body())
}
}
})
}
}
ListFragment.kt
package com.kunalrai.githubtrends
import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kunalrai.githubtrends.databinding.ListFragmentBinding
class ListFragment : Fragment() {
companion object {
fun newInstance() = ListFragment()
}
private val viewModel: ListViewModel by lazy {
ViewModelProviders.of(this).get(ListViewModel::class.java)
}
private lateinit var binding: ListFragmentBinding
var recyclerView: RecyclerView? = null
lateinit var listAdapter: ListAdapter
var repoList: List<Repo> = listOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// viewModel.getRepos().observe(this,
// Observer<List<Repo>> {
// it?.let { repoList ->
// this.repoList = repoList
// Log.i("inside observe",""+repoList)
// }
// })
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getRepos().observe(this,
Observer<List<Repo>> {
it?.let { repoList ->
this.repoList = repoList
Log.i("inside observe",""+repoList)
listAdapter = ListAdapter(context, repoList)
recyclerView?.adapter = listAdapter
}
})
recyclerView = view.findViewById(R.id.rv_repo_list)
recyclerView?.layoutManager = LinearLayoutManager(context)
recyclerView?.setHasFixedSize(true)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = ListFragmentBinding.inflate(inflater, container, false)
binding.lifecycleOwner
binding.viewmodel = viewModel
setHasOptionsMenu(true)
// recyclerView = view?.findViewById(R.id.rv_repo_list)
// recyclerView?.layoutManager = LinearLayoutManager(context)
// recyclerView?.setHasFixedSize(true)
// listAdapter = ListAdapter(context, repoList)
// recyclerView?.adapter = listAdapter
return binding.root
}
}
Repo.kt
package com.kunalrai.githubtrends
import com.squareup.moshi.Json
data class Repo(
#Json(name = "author")
var author: String,
#Json(name = "name")
var name: String,
#Json(name = "description")
var desc: String,
#Json(name = "avatar")
var avatar: String,
#Json(name = "language")
var language: String,
#Json(name = "url")
var url: String,
#Json(name = "stars")
var stars: String,
#Json(name = "forks")
var forks: String
)
list_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="com.kunalrai.githubtrends.ListViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_repo_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</layout>
repo_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<de.hdodenhof.circleimageview.CircleImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/owner_image"
android:layout_width="96dp"
android:layout_height="96dp"
app:civ_border_width="2dp"
app:civ_border_color="#FF000000"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/owner_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/repo_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
Logcat :
2019-11-29 19:15:15.170 20890-20890/? E/Zygote: isWhitelistProcess - Process is Whitelisted
2019-11-29 19:15:15.187 20890-20890/? E/Zygote: accessInfo : 1
2019-11-29 19:15:18.090 20890-20890/com.kunalrai.githubtrends E/RecyclerView: No adapter attached; skipping layout
I have given internet permission in the manifest file. Successfully checked the fetched response before adding recyclerView.
Blank screen is the output with no crash.
I recommend a few changes.
in the ListAdapter
class ListAdapter(private val context: Context?) : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
private val repoList = ArrayList<Repo>() //create an empty list first.
fun resetList(newList: List<Repo>){ //update only when a list is available.
repoList.clear()
repoList.addAll(newList)
notifyDataSetChanged() //you need this part to tell the adapter to redraw the views.
}
... the rest of your List Adapter
then in your Fragment
private lateinit var listAdapter: ListAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = ListFragmentBinding.inflate(inflater, container, false)
binding.lifecycleOwner
binding.viewmodel = viewModel
setHasOptionsMenu(true)
listAdapter = ListAdapter(context) //create adapter with emptyList
recyclerView = view?.findViewById(R.id.rv_repo_list)
recyclerView?.let{
it.layoutManager = LinearLayoutManager(context)
it.setHasFixedSize(true)
it.adapter = listAdapter //we add the adapter here
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getRepos().observe(this,
Observer<List<Repo>> {
it?.let { repoList ->
//you just need to repopulate/recycle the views in your adapter.
//no need to recreate the adapter again.
listAdapter.resetList(repoList)
}
})
}
BASE_URL should be ends with / so add / end of your base url
BASE_URL = "https://github-trending-api.now.sh/"
API not called because you initialized repoList and called api if it's null. so api never call so remove your unwanted if case
var repoList: MutableLiveData<List<Repo>> = MutableLiveData(listOf())
if(repoList == null) {
repoList = MutableLiveData()
loadRepos()
}
call your api inside onviewCreated()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getRepos().observe(this,
Observer<List<Repo>> {
it?.let { repoList ->
listAdapter = ListAdapter(context, repoList)
recyclerView?.adapter = listAdapter
}
})
}