So I'm building this Carousel project that shows an image and some text on the app. I've gotten the structure down by building a prototype. I was able to use Retrofit to call the information from the RestAPI on my terminal as well. But where I have been really stuck at is trying to get the info to show in the container, where I want it to show. I've tried a slew of ways to approach it, but to no avail. Below is the code I have now, which is showing images and text that I put in it. Along with the Retrofit classes. I've been at it for three days and I'm not sure what to do. If anyone has an idea or can help, I'll be extremely thankful.
The API (Retrofit)
package com.examples.carousel.api
import com.examples.carousel.CarouselItem
import com.examples.carousel.utli.Constants.Companion.BASE_URL
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
interface FlickrApi {
#GET("services/rest/?method=flickr.interestingness.getList" +
"&api_key=(private)" +
"&format=json" +
"&nojsoncallback=1" +
"&extras=url_s")
fun getCarouselItem() : Call<List<CarouselItem>>
companion object {
fun create() : FlickrApi {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
return retrofit.create(FlickrApi::class.java)
}
}
}
The Model (The comment out code is for the info I want from the api)
package com.examples.carousel
data class CarouselItem internal constructor(
// var title: String,
// var id: String,
// var url_s: String
var image: Int,
var title2: String
) {
}
The Adapter
package com.examples.carousel
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide
import com.makeramen.roundedimageview.RoundedImageView
class ItemAdapter internal constructor(carouselItems: MutableList<CarouselItem>, viewPager2: ViewPager2) : RecyclerView.Adapter<ItemAdapter.ItemPagerViewHolder>() {
private val carouselItems: List<CarouselItem>
private val viewPager2: ViewPager2
init {
this.carouselItems = carouselItems
this.viewPager2 = viewPager2
}
class ItemPagerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val photo: RoundedImageView = itemView.findViewById(R.id.picture)
private val title: TextView = itemView.findViewById(R.id.photo_name)
fun image(carouselItem: CarouselItem) {
photo.setImageResource(carouselItem.image)
}
fun text(carouselItem: CarouselItem) {
title.text = carouselItem.title2
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemPagerViewHolder {
return ItemPagerViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.carousel_item_container,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ItemPagerViewHolder, position: Int) {
holder.image(carouselItems[position])
holder.text(carouselItems[position])
if (position == carouselItems.size - 2) {
viewPager2.post(runnable)
}
}
override fun getItemCount(): Int {
return carouselItems.size
}
private val runnable = Runnable {
carouselItems.addAll(carouselItems)
notifyDataSetChanged()
}
}
The MainActivity
package com.examples.carousel
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.CompositePageTransformer
import androidx.viewpager2.widget.MarginPageTransformer
import androidx.viewpager2.widget.ViewPager2
import com.examples.carousel.api.FlickrApi
import com.makeramen.roundedimageview.RoundedImageView
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlin.math.abs
class CarouselActivity : AppCompatActivity() {
lateinit var viewPager: ViewPager2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_carousel)
viewPager = findViewById(R.id.viewPager_ImageSlider)
supportActionBar?.hide()
val multipleItems: MutableList<CarouselItem> = ArrayList()
multipleItems.add(CarouselItem(R.drawable.bcw_38,"Image 1"))
multipleItems.add(CarouselItem(R.drawable.bcw_39,"Image 2"))
multipleItems.add(CarouselItem(R.drawable.bcw_40,"Image 3"))
multipleItems.add(CarouselItem(R.drawable.bcw_45,"Image 4"))
multipleItems.add(CarouselItem(R.drawable.bcw_50,"Image 5"))
multipleItems.add(CarouselItem(R.drawable.bcw_53,"Image 6"))
viewPager.adapter = ItemAdapter(multipleItems,viewPager)
viewPager.clipToPadding = false
viewPager.clipChildren = false
viewPager.offscreenPageLimit = 3
viewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(MarginPageTransformer(30))
compositePageTransformer.addTransformer { page, position ->
val r = 1 - abs(position)
page.scaleY = 0.85f + r * 0.25f
}
viewPager.setPageTransformer(compositePageTransformer)
//
// val apiInterface = FlickrApi.create().getCarouselItem()
//
// apiInterface.enqueue(object: Callback<List<CarouselItem>> {
// override fun onFailure(call: Call<List<CarouselItem>>, t: Throwable) {
//
// }
//
// override fun onResponse(call: Call<List<CarouselItem>>, response: Response<List<CarouselItem>>) {
//
// if (response.body() != null){
// itemAdapter.setItemListItems(response.body()!!)
//
// }
// }
// })
}
}
The XML Files:
The ViewPager2 layout
<?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" android:background="#232323"
android:id="#+id/linearLayout">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPager_ImageSlider"
android:layout_height="0dp"
android:layout_width="0dp"
android:paddingStart="70dp"
android:paddingEnd="70dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginTop="80dp"/>
<TextView
android:text="#string/osa_playlist"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="#+id/textView2"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="40dp"
android:textColor="#color/white" android:textSize="25sp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
The Container
<?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" android:id="#+id/container" android:background="#232323">
<com.makeramen.roundedimageview.RoundedImageView
android:layout_width="match_parent"
android:layout_height="550dp" app:layout_constraintTop_toTopOf="parent" android:id="#+id/picture"
android:adjustViewBounds="true"
app:riv_corner_radius="12dp" tools:layout_editor_absoluteX="0dp"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content" android:id="#+id/photo_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" android:layout_marginLeft="8dp"
android:layout_marginStart="8dp" android:layout_marginRight="8dp" android:layout_marginEnd="8dp"
android:textStyle="bold"
tools:text="TEXT TEXT" android:textSize="36sp" android:textColor="#color/white" android:gravity="center"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintTop_toBottomOf="#+id/picture" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="230dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
The Dependencies
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
//For the Carousel Container
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'com.makeramen:roundedimageview:2.3.0'
//For Retrofit and Gson
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// implementation 'com.squareup.retrofit2:converter-scalars:2.5.0'
// implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
testImplementation 'junit:junit:4.13.2'
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
}
*Its not the Internet permission, it's already there.
itemAdapter.setItemListItems(response.body()!!) instead of calling adapters method, write your own method which updates the list:
private val carouselItems: List,
make this list mutable and write method:
public fun updateList(val items: List<CarouselItem>){
carouselItems.clear()
carouselItems.addAll(items)
notifyDataSetChanged()
}
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)
}
...
}
The App is working fine The JSON data has also been fetched but the recycler view not showing the data even the recycler view is also not showing.
Here Im Sharing my code below please help me to find out the problem,
My app does not show any error while compiling, all app working completely fine.
course_rv_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
enter code herexmlns: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_gravity="center"
android:layout_margin="5dp"
app:cardCornerRadius="5dp"
app:cardElevation="4dp">
<!--on below line we are creating a
linear layout for grid view item-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!--on below line we are creating a simple image view-->
<ImageView
android:id="#+id/idIVCourse"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center"
android:layout_margin="8dp"
android:padding="5dp"
android:src="#mipmap/ic_launcher" />
<!--on below line we are creating a simple text view-->
<TextView
android:id="#+id/idTVCourse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:padding="4dp"
android:text="#string/app_name"
android:textAlignment="textStart"
android:textColor="#color/black"
android:textStyle="bold"
tools:ignore="RtlCompat" />
</LinearLayout>
</androidx.cardview.widget.CardView>
activity_contact.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ContactActivity">
<ProgressBar
android:id="#+id/idPBLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/idRVCourses"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</RelativeLayout>
Contact_Activity.kt
import android.app.DownloadManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import org.json.JSONObject
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.android.volley.toolbox.JsonArrayRequest
import com.denzcoskun.imageslider.models.SlideModel
import androidx.recyclerview.widget.LinearLayoutManager;
import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import org.json.JSONArray;
import org.json.JSONException;
import java.util.ArrayList;
class ContactActivity : AppCompatActivity() {
private val BASE_URL = "MyURL"
lateinit var courseRV: RecyclerView
lateinit var loadingPB: ProgressBar
lateinit var courseRVAdapter: CourseRVAdapter
lateinit var courseList: ArrayList<CourseRVModal>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_contact)
// on below line we are initializing
// our variable with their ids.
courseRV = findViewById(R.id.idRVCourses)
loadingPB = findViewById(R.id.idPBLoading)
// on below line we are initializing our list
courseList = ArrayList()
// on below line we are initializing our adapter.
courseRVAdapter = CourseRVAdapter(courseList)
// on below line we are setting
// adapter to recycler view.
courseRV.adapter = courseRVAdapter
// on below line we are calling
// get data method to get data.
getData()
}
private fun getData() {
// on below line we are creating a variable for url
var url = BASE_URL + "getSlider.php"
// on below line we are creating a
// variable for our request queue
val queue = Volley.newRequestQueue(this#ContactActivity)
// on below line we are creating a request
// variable for making our json object request.
val request =
// as we are getting json object response and we are making a get request.
JsonArrayRequest(Request.Method.GET, url, null, { response ->
// this method is called when we get successful response from API.
loadingPB.visibility = View.GONE
try {
for (i in 0 until response.length()) {
// on below line we are extracting
// data from each json object
val respObj = response.getJSONObject(i)
val langName = respObj.getString("sliderTitle")
val langImg = respObj.getString("sliderImage")
// on below line we are adding data to our list
courseList.add(CourseRVModal(langName, langImg))
// on below line we are notifying
// our adapter that data has updated.
courseRVAdapter.notifyDataSetChanged()
}
// on below line we
// are handling exception
} catch (e: Exception) {
e.printStackTrace()
}
}, { error ->
// in this case we are simply displaying a toast message.
Toast.makeText(this#ContactActivity, "Fail to get response", Toast.LENGTH_SHORT)
.show()
})
// at last we are adding our
// request to our queue.
queue.add(request)
}
}
CourseRVModel.kt
data class CourseRVModal(
// on below line we are creating a two variable
// one for course name and other for course image.
var courseName: String,
var courseImg: String
)
CourseRVAdaptor.kt
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.squareup.picasso.Picasso
class CourseRVAdapter(
// on below line we are passing variables as course list and context
private var courseList: ArrayList<CourseRVModal>,
) : RecyclerView.Adapter<CourseRVAdapter.CourseViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): CourseRVAdapter.CourseViewHolder {
// this method is use to inflate the layout file
// which we have created for our recycler view.
// on below line we are inflating our layout file.
val itemView = LayoutInflater.from(parent.context).inflate(
R.layout.course_rv_item,
parent, false
)
// at last we are returning our view
// holder class with our item View File.
return CourseViewHolder(itemView)
}
override fun onBindViewHolder(holder: CourseRVAdapter.CourseViewHolder, position: Int) {
// on below line we are setting data to our text view and our image view.
holder.courseNameTV.text = courseList.get(position).courseName
Picasso.get().load(courseList.get(position).courseImg).into(holder.courseIV)
}
override fun getItemCount(): Int {
// on below line we are returning
// our size of our list
return courseList.size
}
class CourseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
// on below line we are initializing our course name
// text view and our image view.
val courseNameTV: TextView = itemView.findViewById(R.id.idTVCourse)
val courseIV: ImageView = itemView.findViewById(R.id.idIVCourse)
}
}
build.gradle
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.firebase:firebase-database:20.0.6'
implementation 'com.google.firebase:firebase-config-ktx:21.1.2'
implementation 'com.google.firebase:firebase-config:21.1.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.github.denzcoskun:ImageSlideshow:0.1.0'
implementation("com.android.volley:volley:1.2.1")
implementation 'com.squareup.picasso:picasso:2.71828'
In activity_contact.xml your recycler view idRVCourses has attribute android:visibility="gone". So it is hidden when the screen first loads. You need make it visible once you load the data. for that add courseRV.visibility = View.GONE after loadingPB.visibility = View.GONE in getData function.
i'd like to know what i'm doing wrong over here. I'm building a CRUD app in Kotlin, and i'm using the recyclerview to make the readData page. The problem is, when i'm on the readData page doesn't show me anything, just the text views, so i debugged and when i join in the page, show this message: "Recyclerview No adapter attached; skipping layout".
Here's my code:
(i'm brazilian, so, some words are in portuguese, but you'll get it.)
verDados.kt
package com.nicolas.csrd
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.google.firebase.database.*
import kotlinx.android.synthetic.main.activity_ver_database.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class verDados : AppCompatActivity() {
private lateinit var database: FirebaseDatabase
private lateinit var reference: DatabaseReference
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_ver_database)
database = FirebaseDatabase.getInstance()
reference = database.getReference("usuarios")
verDados()
btn_voltar.setOnClickListener() {
startActivity(Intent(this#verDados, Dashboard::class.java))
finish()
}
}
private fun verDados() {
reference.addValueEventListener(object: ValueEventListener{
override fun onCancelled(p0: DatabaseError) {
Log.e("cancelar", p0.toString())
}
override fun onDataChange(p0: DataSnapshot) {
//Colocando os usuarios numa lista
var list = ArrayList<DatabaseModelo>()
for (data in p0.children) {
val model = data.getValue(DatabaseModelo::class.java)
list.add(model as DatabaseModelo)
}
if (list.size > 0) {
val ususariosModelo = usuariosModelo(list)
recyclerview.adapter = ususariosModelo
}
}
})
}
}
DatabaseModel
package com.nicolas.csrd
class DatabaseModelo() {
lateinit var email: String
lateinit var senha: String
constructor(email: String, senha: String) : this() {
this.email = email
this.senha = senha
}
}
usuariosModelo.kt (recyclerview adapter)
package com.nicolas.csrd
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.menu.ActionMenuItemView
import androidx.appcompat.view.menu.MenuView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.usuarios_modelo.view.*
class usuariosModelo(val list: ArrayList<DatabaseModelo>): RecyclerView.Adapter<usuariosModelo.ViewHolder>() {
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val email = itemView.campo_email
val senha = itemView.campo_senha
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.usuarios_modelo, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.email.text = list[position].email
holder.senha.text = list[position].senha
}
override fun getItemCount(): Int {
return list.size
}
}
----- XML FILES -----
activity_ver_database.xml (read data page)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/dark"
tools:context=".Login">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal" >
<ImageView
android:id="#+id/btn_voltar"
android:layout_width="50dp"
android:layout_height="30dp"
android:layout_gravity="start"
android:layout_marginStart="5dp"
android:layout_marginTop="23dp"
android:src="#drawable/back_arrow" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DADOS"
android:textColor="#7ec1d1"
android:textSize="30sp"
android:textStyle="bold"
android:layout_marginTop="20dp"
android:letterSpacing="0.15"
android:fontFamily="#font/montserrat_medium"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Todos os usuários cadastrados"
android:textColor="#7ec1d1"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginTop="0dp"
android:fontFamily="#font/montserrat_thin"
/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/recyclerview"
android:layout_margin="10sp"/>
<TextView
android:id="#+id/btn_cadastrar"
android:layout_width="350dp"
android:layout_height="50dp"
android:layout_marginTop="30dp"
android:backgroundTint="#7ec1d1"
android:gravity="center_horizontal"
android:fontFamily="#font/montserrat_regular"
android:text="Deseja cadastrar alguém? Clique aqui."
android:textColor="#color/white2"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout>
You are getting this error because you have no defined an adaptor for the RecyclerView that you are using. Somewhere inn your onCreate method, create the adaptor and set it to the RecyclerView. Something like:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_ver_database)
recyclerView = findViewById(R.id.recyclerview)
recyclerView.apply {
adapter = usuariosModelo(listOfItems)
}
database = FirebaseDatabase.getInstance()
reference = database.getReference("usuarios")
verDados()
btn_voltar.setOnClickListener() {
startActivity(Intent(this#verDados, Dashboard::class.java))
finish()
}
}
i solve the problem just adding the tag below inside the recyclerview tag in the .xml file:
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
I hope that help y'all!
I'm sorry to ask the repeatedly answered question but I just couldn't solve this relating to my specific case, maybe I'm missing something. The error is E/RecyclerView: No adapter attached; skipping layout and I'm not sure, is the problem with an adapter I set or the RecyclerView per se? Also, I was following a tutorial and this was the code that was presented.
(I tried brining the initRecyclerView() into the main onCreateView but no luck. Some answers say to set an empty adapter first and notify it with the changes later but I don't know how to do that.)
This is my HomeFragment:
open class HomeFragment() : Fragment() {
private lateinit var homeViewModel: HomeViewModel
private lateinit var binding: FragmentHomeBinding
private val language = arrayOf("English", "German", "Arabic", "Spanish", "Chinese", "French")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
val dao = VocabData.getInstance(this).VocabDao
val repository = VocabRepository(dao)
val factory = HomeViewModelFactory(repository, application = activity?.applicationContext as Application)
homeViewModel = ViewModelProvider(this, factory).get(HomeViewModel::class.java)
binding.myViewModel = homeViewModel
binding.lifecycleOwner = this
initRecyclerView()
homeViewModel.message.observe(viewLifecycleOwner, Observer {
it.getContentIfNotHandled()?.let {
Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show()
}
})
// create an adapter
val arrayAdapter =
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, language)
binding.spinner.adapter = arrayAdapter
// Set layout to use when the list of choices appear
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// Set Adapter to Spinner
binding.spinner.setAdapter(arrayAdapter)
val button = binding.tagButton
button.setOnClickListener{
GlobalScope.launch(Dispatchers.IO) {
homeViewModel.tagger(binding.spinner)
}
}
return binding.root
}
private fun initRecyclerView(){
binding.vocabRecyclerView.layoutManager = LinearLayoutManager(requireContext())
displayVocabsList()
}
private fun displayVocabsList() {
homeViewModel.vocabs.observe(viewLifecycleOwner, Observer {
Log.i("MYTAG", it.toString())
binding.vocabRecyclerView.adapter = MyRecyclerViewAdapter(it, { selectedItem: Vocab -> listItemClicked(selectedItem) })
})
}
private fun listItemClicked(vocab: Vocab){
Toast.makeText(requireContext(), "selected sentence is ${vocab.sentString}", Toast.LENGTH_LONG).show()
}
}
And this is my RecyclerViewAdapter, which is really just boilerplate code:
class MyRecyclerViewAdapter(private val vocabsList: List<Vocab>, private val clickListener:(Vocab)->Unit) :
RecyclerView.Adapter<MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding : ListItemBinding =
DataBindingUtil.inflate(layoutInflater, R.layout.list_item, parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(vocabsList[position], clickListener)
}
override fun getItemCount(): Int {
return vocabsList.size
}
}
class MyViewHolder(private val binding: ListItemBinding):RecyclerView.ViewHolder(binding.root){
fun bind(vocab: Vocab, clickListener:(Vocab)->Unit){
binding.sentenceTextView.text = vocab.sentString
binding.listItemLayout.setOnClickListener{
clickListener(vocab)
}
}
}
I think that's all the related code. If you have an idea please let me know how to avoid this error/lag, thank you!
UPDATE
activity_main.xml
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/bottom_nav_menu" >
<androidx.viewpager2.widget.ViewPager2
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.bottomnavigation.BottomNavigationView>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
class = "androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
fragment_home.xml
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="myViewModel"
type="com.jwanhsulaiman.talktag.ui.home.HomeViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"
tools:context=".ui.home.HomeFragment">
<EditText
android:id="#+id/editTextTextMultiLine"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ems="10"
android:gravity="start|top"
android:hint="Input Sentence(s)\n"
android:inputType="textMultiLine"
android:text="#={myViewModel.inputVocab}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/spinner" />
<Button
android:id="#+id/tag_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:enabled="#{myViewModel.enabled}"
android:text="#={myViewModel.tagAll}"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/editTextTextMultiLine" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/vocab_recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/progressBar" />
<Button
android:id="#+id/clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:onClick="#{()->myViewModel.deleteAll()}"
android:text="CLEAR"
app:layout_constraintEnd_toStartOf="#+id/progressBar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/editTextTextMultiLine" />
<Spinner
android:id="#+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="#+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="#{!myViewModel.barProgress}"
app:layout_constraintEnd_toStartOf="#+id/tag_button"
app:layout_constraintTop_toBottomOf="#+id/editTextTextMultiLine" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
HomeViewModel.kt
import android.annotation.SuppressLint
import android.app.Application
import android.view.View
import android.widget.Spinner
import androidx.databinding.Bindable
import androidx.databinding.BindingAdapter
import androidx.databinding.Observable
import androidx.databinding.ObservableField
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.jwanhsulaiman.talktag.Event
import com.jwanhsulaiman.talktag.R
import com.jwanhsulaiman.talktag.database.Vocab
import com.jwanhsulaiman.talktag.database.VocabRepository
import edu.stanford.nlp.tagger.maxent.MaxentTagger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import opennlp.tools.sentdetect.SentenceDetectorME
import opennlp.tools.sentdetect.SentenceModel
#BindingAdapter("android:visibility")
fun setVisibility(view: View, visible: Boolean) {
view.visibility = if (visible) View.INVISIBLE else View.VISIBLE
}
#BindingAdapter("android:enabled")
fun setEnabled(view: View, enabled: Boolean) {
view.isEnabled = !enabled
}
#SuppressLint("StaticFieldLeak")
class HomeViewModel(private val repository: VocabRepository, application: Application) : AndroidViewModel(
application
), Observable {
private val context = getApplication<Application>().applicationContext
private val model: SentenceModel = SentenceModel(context.resources.openRawResource(R.raw.en))
private val sDetector = SentenceDetectorME(model)
private var senlist = mutableListOf<String?>()
val vocabs = repository.vocabs
#Bindable
var barProgress = ObservableField<Boolean>()
#Bindable
var enabled = ObservableField<Boolean>()
private fun makeVisible(){
this.barProgress.set(true) }
private fun makeInvisible(){
this.barProgress.set(false) }
private fun makeEnabled(){
this.enabled.set(true) }
private fun makeDisabled(){
this.enabled.set(false) }
#Bindable
val inputVocab = MutableLiveData<String>()
#Bindable
val tagAll = MutableLiveData<String>()
private val statusMessage = MutableLiveData<Event<String>>()
val message : LiveData<Event<String>>
get() = statusMessage
init {
tagAll.postValue("Tag!")
}
suspend fun tagger(spinner: Spinner){
if (inputVocab.value.isNullOrBlank()) {
statusMessage.postValue(Event("Please enter sentence"))
} else {
withContext(Dispatchers.IO) {
//tag words
makeEnabled()
makeVisible()
}
}
}
private suspend fun splitSens(vocab: String): MutableList<String?> {
withContext(Dispatchers.IO) {
//split sentences
}
return senlist
}
private suspend fun tagAll(vocab: String){
withContext(Dispatchers.IO){
insert(Vocab(0, vocab))
inputVocab.postValue(null)
}
makeInvisible()
makeDisabled()
}
fun insert(vocab: Vocab) : Job = viewModelScope.launch {
repository.insert(vocab)
statusMessage.value = Event("Vocab inserted successfuly")
}
fun update(vocab: Vocab) : Job = viewModelScope.launch {
repository.update(vocab)
}
fun delete(vocab: Vocab) : Job = viewModelScope.launch {
repository.delete(vocab)
}
fun deleteAll() = viewModelScope.launch {
repository.deleteAll()
}
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
//TODO("Not yet implemented")
}
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
//TODO("Not yet implemented")
}
}
build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
/**
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
**/
android {
compileSdkVersion 29
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.jwanhsulaiman.talktag"
minSdkVersion 16
targetSdkVersion 29
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
dataBinding true
}
}
dependencies {
implementation files('libs/postagger.jar')
implementation files('libs/nlp/opennlp-tools-1.9.3.jar')
def lifecycle_version = "2.2.0"
def room_version = "2.2.3"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'androidx.navigation:navigation-fragment:2.3.3'
implementation 'androidx.navigation:navigation-ui:2.3.3'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.3'
implementation 'androidx.room:room-runtime:2.2.6'
testImplementation 'junit:junit:4.13.2'
kapt "com.android.databinding:compiler:3.5.0"
implementation 'androidx.fragment:fragment-ktx:1.3.0'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Annotation processor
//noinspection LifecycleAnnotationProcessorWithJava8
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
//coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
kapt 'androidx.room:room-compiler:2.2.6'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0"
// Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:2.2.6"
implementation 'com.android.support:multidex:1.0.3'
}
Ok, it's normal you have this message because in your code, you' ll do this :
homeViewModel.vocabs.observe(viewLifecycleOwner, Observer {
Log.i("MYTAG", it.toString())
binding.vocabRecyclerView.adapter = MyRecyclerViewAdapter(it, { selectedItem: Vocab -> listItemClicked(selectedItem) })
})
The best solution to not have this message is to set an adapter in createView :
myRecyclerViewAdapter = MyRecyclerViewAdapter({ selectedItem: Vocab -> listItemClicked(selectedItem) })
binding.vocabRecyclerView.adapter = myRecyclerViewAdapter
and :
homeViewModel.vocabs.observe(viewLifecycleOwner, Observer {
myRecyclerViewAdapter.addData(it)
}
and for recyclerView :
class MyRecyclerViewAdapter : .... {
private var values = arrayListOf<....>()
fun addData(values : List<>){
values.addAll(values)
}
}
NB: I put addAll but I don't know what you do, may be you have to do something else.
You have this message because there's no adapter. You have an adpater only when there're data with your observe. It's better if you initialize your adapter and after listening for new datas.
}
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()