I'm new to kotlin and I could not find related solutions.
What I want to do is get the data from api(amount and currency) and show it onclick.
I was able to loop data but I don't know how to unloop.
The response from api is this:
{
"data": {
"amount": 825,
"currency": "hkd"
}
}
My Model:
data class MainData(
var data: AmountData
)
data class AmountData(
val amount: Int,
val currency: String,
)
My ApiService:
interface ApiService {
#GET("posts")
fun getPosts(): Call<MutableList<PostModel>>
#GET("checkout/vend/CLIENT_ID/payment/request")
fun paymentRequest(): Call<MainData>
}
My Adapter:
class PaymentAdapter(private val mainData: MainData): RecyclerView.Adapter<PaymentViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaymentViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.card_post, parent, false)
return PaymentViewHolder(view)
}
override fun onBindViewHolder(holder: PaymentViewHolder, position: Int) {
return holder.bindView(mainData) // I don't even know how to bind the data
}
override fun getItemCount(): Int {
return mainData.data.amount // This is also incorrect but I don't know what to do
}
}
class PaymentViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
private val tvAmount: TextView = itemView.findViewById(R.id.tvAmount)
private val tvCurrency: TextView = itemView.findViewById(R.id.tvCurrency)
fun bindView(mainData: MainData){
tvAmount.text = mainData.data.amount.toString()
tvCurrency.text = mainData.data.currency
}
}
This is the result so far.
because you only have 1 item always you could just do
override fun getItemCount(): Int {
return 1
}
And it might give already exactly what you want
Though, it really is unnecessary to use a RecyclerView for this then. I would remove the RecyclerView and just add two TextViews or something.
Related
I am using the getItemViewType() in my adapter to display a different view
when the user sends a message and
when the user receives a message
in my chatting application. The problem I'm facing is when I have to load the senders and receivers profile photo next to their message. When I give both the ImageView a different id and use them as below,
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val chat:Chat = chat[position]
holder.showMessage.text = chat.message
Picasso.get().load(chat.userPic).into(holder.userPic)
Picasso.get().load(chat.recPic).into(holder.recPic)
}
class ViewHolder(v:View) : RecyclerView.ViewHolder(v){
val showMessage:TextView = v.findViewById(R.id.showMessage)
val userPic:CircleImageView = v.findViewById(R.id.userPic)
val recPic:CircleImageView = v.findViewById(R.id.recPic)
}
I get a error saying one of the id is null. When i give both the ImageView's the same id and use them like below
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val chat:Chat = chat[position]
holder.showMessage.text = chat.message
Picasso.get().load(chat.userPic).into(holder.userPic)
Picasso.get().load(chat.recPic).into(holder.userPic)
}
class ViewHolder(v:View) : RecyclerView.ViewHolder(v){
val showMessage:TextView = v.findViewById(R.id.showMessage)
val userPic:CircleImageView = v.findViewById(R.id.userPic)
//val recPic:CircleImageView = v.findViewById(R.id.recPic)
}
it works but sometimes the image is displayed on the wrong ImageView since both the ImageView's have the same id.
A solution that I thought of was to retrieve the currently logged in user's(senders) photo url from the database and store that value in a string in the adapter like below,
val abc = Firebase.firestore
var userPic:String = ""
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if(viewType == MSGTYPERIGHT){
val view1:View = LayoutInflater.from(parent.context).inflate(R.layout.chat_to_row,parent,false)
ViewHolder(view1)
}else{
val view2:View = LayoutInflater.from(parent.context).inflate(R.layout.chat_from_row,parent,false)
ViewHolder(view2)
}
}
override fun getItemCount(): Int {
return chat.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val chat:Chat = chat[position]
abc.collection("users").document(Firebase.auth.uid!!).addSnapshotListener { value, _ ->
userPic = value?.get("profileUrl").toString()
}
holder.showMessage.text = chat.message
Picasso.get().load(userPic).into(holder.userPic)
Picasso.get().load(chat.recPic).into(holder.recPic)
}
class ViewHolder(v:View) : RecyclerView.ViewHolder(v){
val showMessage:TextView = v.findViewById(R.id.showMessage)
val userPic:CircleImageView = v.findViewById(R.id.userPic)
val recPic:CircleImageView = v.findViewById(R.id.recPic)
}
but the i get a error message from Picasso saying the path is empty, meaning the variable userPic contains the empty string i initialized it with.
MessageAdapter.kt
class MessageAdapter(private val chat: ArrayList<Chat>):RecyclerView.Adapter<MessageAdapter.ViewHolder>() {
private val MSGTYPELEFT = 0
private val MSGTYPERIGHT = 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if(viewType == MSGTYPERIGHT){
val view1:View = LayoutInflater.from(parent.context).inflate(R.layout.chat_to_row,parent,false)
ViewHolder(view1)
}else{
val view2:View = LayoutInflater.from(parent.context).inflate(R.layout.chat_from_row,parent,false)
ViewHolder(view2)
}
}
override fun getItemCount(): Int {
return chat.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
}
class ViewHolder(v:View) : RecyclerView.ViewHolder(v){
val showMessage:TextView = v.findViewById(R.id.showMessage)
val userPic:CircleImageView = v.findViewById(R.id.userPic)
val recPic:CircleImageView = v.findViewById(R.id.recPic)
}
override fun getItemViewType(position: Int): Int {
val fuser = Firebase.auth
return if (chat[position].userId == fuser.uid) {
MSGTYPERIGHT
} else {
MSGTYPELEFT
}
}
}
Is there something I'm doing wrong or do you guys have any idea how to solve this issue?
You're using getItemViewType() but not differentiating between the layouts when accessing widgets which is why you're facing the issue.
Change Your onBindViewHolder() as:
when(holder) {
is MSGTYPERIGHT -> {
//Put your code related to this ItemViewType's layout here
}
else {
//Put your other layout's code here
//You can use one condition like above for left as well, though.
}
}
When you use when or even if else to check the type of the holder, in that scope, IDE automatically casts your holder to that type, so, in the first case of when, it will be cast to MSGTYPERIGHT, then, you'll be able to access holder specific functions and IDE will highlight the holder variable in that scope. For example, like the below image:
I am doing a school project.
I have a list with Doses, so I need to fetch data en set text one by one.
Right now I'm getting:
kotlin.UninitializedPropertyAccessException: lateinit property medicine has not been initialized.
So I need to wait till the first item is fetched and set before continuing to next item.
can you help me?
class ClientDoseListAdapter(private val doses: List<Dose>) : RecyclerView.Adapter<ClientDoseListAdapter.ViewHolder>() {
private lateinit var medicine : Medicine
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.client_dose_listitem, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = doses[position]
runBlocking {
displayMedicine(item.medicine)
}
holder.med_name.text = medicine.name
holder.dose_amount.text = item.amount.toString()
}
private suspend fun displayMedicine(id: Int) {
fetchMedicine(id)
}
override fun getItemCount(): Int = doses.size
inner class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView),
LayoutContainer
private fun fetchMedicine(id: Int) {
service.getMedicine(id, "Bearer ${ClienOverzichtFragment.auth}")
.enqueue(object : Callback<List<Medicine>> {
override fun onResponse(call: Call<List<Medicine>>, response: Response<List<Medicine>>) {
if (response.code() == 200) {
val temp = response.body()!!
medicine = temp[0]
Log.v("SHIT", medicine.name)
} else {
Log.v("SHIT", response.code().toString())
//TODO
}
}
override fun onFailure(call: Call<List<Medicine>>, t: Throwable) {
Log.v("SHIT", "FAILED : "+t.message)
}
})
}
}
Move your service call out of the Recycler (best into a ViewModel, but can call from Activity or using any other pattern - the main thing, shouldn't be part of the Recycler) and pass the data, when it's received, into the Recycler.
Your ClientDoseListAdapter to accept medicine:
class ClientDoseListAdapter(private val doses: List<Dose>, private val medicine: Medicine)
In your activity, initiate and a call for medicine and observe it - when the data arrives, pass it to the adapter. Assuming you use a view model your code in Activity would look something like this:
viewModel.getMedicine().observe(
this,
Observer<Medicine> { medicine ->
//assuming doses come from somewhere else
adapter = ClientDoseListAdapter(doses, medicine, this)
clientDoseRecyclerView.adapter = adapter
}
)
I have 3 similars adapters and I want to combine them in 1. The only thing that is different is the ArrayList type. I have 2 room entities for room, FavoriteArticle, and HistoryArticle, but they have the same fields as WikiPage.
class Adapter<T> (#LayoutRes private val layoutRes: Int) : RecyclerView.Adapter<Holder>() {
val currentResult: ArrayList<T> = ArrayList()
override fun getItemCount() = currentResult.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val cardItem = LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
return Holder(cardItem)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val page = currentResult[position]
holder.updateWithPage(page)
}
fun add(wiki: ArrayList<T>?) { {
currentResult.clear()
if (wiki != null) {
currentResult.addAll(wiki)
}
}
}
Holder class:
class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val articleImageView: ImageView = itemView.findViewById(R.id.articleCardItemImage)
private val titleTextView: TextView = itemView.findViewById(R.id.articleCardItemTitle)
private lateinit var currentPage: WikiPage
init {
itemView.setOnClickListener {
itemView.context.startActivity(ArticleDetailActivity.createStartIntent(itemView.context, currentPage))
}
}
fun updateWithPage(page: WikiPage) {
currentPage = page
titleTextView.text = page.title
Picasso.get().load(page.thumbnail.source).into(articleImageView)
}
}
I can't figure out how to modify my updateWithPage function from onBindViewHolder using generics. How can I do this?
Some idea for your reference by using Interface:
interface Article {
getWikiPage(): WikiPage
}
class FavoriteArticle : Article {
getWikiPage() = wikiPage
}
class HistoryArticle: Article {
getWikiPage() = wikiPage
}
// Adapter
class Adapter<Article>
...
override fun onBindViewHolder(holder: Holder, position: Int) {
val page = currentResult[position].getWikiPage()
holder.updateWithPage(page)
}
...
You can make a generalized adapter that can accept any viewholder class. This will require pushing the viewholder creation logic inside the viewholder class itself. Using some kotlin extension functions can even be a lot cleaner for this use case.
Check out this link: https://thecommonwise.com/blogs/60f6ea9bea3d10001503eac3
I want each row of my RecyclerView to display all the details of one document of the collection.
I've used this exact same adapter code, albeit with a different class to serialize into. And it works well. But in this instance, it's simply not working.
But the code just doesn't get into populating the views.
My database is like:
reviews--Orange--vault--|
|-firstReview
|-secondReview
|-sjdeifhaih5aseoi
...
My query and adapter from the fragment:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ReviewViewModel::class.java)
val reviewQuery = FirebaseFirestore.getInstance().collection("reviews").document("Orange").collection("vault")
val reviewBurnOptions = FirestoreRecyclerOptions.Builder<Review>()
.setQuery(reviewQuery, object : SnapshotParser<Review> {
override fun parseSnapshot(snapshot: DocumentSnapshot): Review {
return snapshot.toObject(Review::class.java)!!.also {
it.id = snapshot.id
}
}
}).setLifecycleOwner(this)
reviewRecycler.adapter=ReviewBurnAdapter(reviewBurnOptions.build())}
class ReviewBurnAdapter(options: FirestoreRecyclerOptions<Review>) :
FirestoreRecyclerAdapter<Review, ReviewBurnAdapter.ViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {//I never reach this point
val view = LayoutInflater.from(parent.context).inflate(R.layout.row_review, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, item: Review) {
holder.apply {
holder.itemView.rowAuthor.text = item.author
}
}
inner class ViewHolder(override val containerView: View) :
RecyclerView.ViewHolder(containerView), LayoutContainer
}
Class to serialize into:
import com.google.firebase.firestore.Exclude
import com.google.firebase.firestore.PropertyName
import java.util.*
class Review(
#get:Exclude var id: String = "DEVIL",
#JvmField #PropertyName(AUTHOR) var author: String = "",
#JvmField #PropertyName(WRITEUP) var writeup: String = "",
//#JvmField #PropertyName(MOMENT) var moment:Date=Date(1997,12,1),
#JvmField #PropertyName(RATING) var rating: Int = 0
) {
companion object {
const val AUTHOR = "author"
const val WRITEUP = "writeup"
const val RATING = "rating"
//const val MOMENT="moment"
}
}
Also, there's no errors, it just never reaches the code that would generate and populate with viewHolders.
Alright, the fix was ultra simple, as #Prashant Jha pointed out, I hadn't specified a layout manager for my RecyclerView -_-
To be crystal clear, I added app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
to my xml, and everything worked.
I want to fetch some json data, see in the image the green arrow:
The problem is that Android Studio doesn't let me get the data I want. It stops until a step before (I think). In my adapter class check:
holder?.view?.textWeather?.text = weatherFor.weather.toString()
Also it shows me in the emulator the red arrow, what is this?
Below is my main Activity's json method with the classes i want to fetch data for, and the associated Adapter class.
Main Activity
fun fetchJson() {
val url="https://api.openweathermap.org/data/2.5/forecast?q=Prague,CZ&appid=4cf7f6610d941a1ca7583f50e7e41ba3"
val request=Request.Builder().url(url).build()
val client= OkHttpClient()
client.newCall(request).enqueue(object :Callback {
override fun onResponse(call: Call?, response: Response?) {
val body=response?.body()?.string()
println(body)
val gson=GsonBuilder().create()
val forecastfeed=gson.fromJson(body,ForecastFeed::class.java)
runOnUiThread{
recyclerView_main.adapter=MainAdapter(forecastfeed)
}
}
override fun onFailure(call: Call?, e: IOException?) {
println("Failed to execute request")
}
})
}
class ForecastFeed(val list:List<ForecastWeatherList>) { }
class ForecastWeatherList(val weather:List<WeatherData>) { }
class WeatherData(val main:String,val icon:String) { }
Adapter
class MainAdapter(val forecastfeed: ForecastFeed): RecyclerView.Adapter<CustomViewHolder>() {
val forecastWeather = listOf<String>("First","Second")
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val weatherFor = forecastfeed.list.get(position)
holder?.view?.textWeather?.text = weatherFor.weather.toString()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder{
//how do we even create a view
val layoutInflater =LayoutInflater.from(parent?.context)
val cellForRow=layoutInflater.inflate(R.layout.weather_row,parent,false)
return CustomViewHolder(cellForRow)
}
override fun getItemCount(): Int {
return forecastfeed.list.count()
}
}
class CustomViewHolder(val view: View):RecyclerView.ViewHolder(view) { }
You can format the data manually
holder?.view?.textWeather?.text = "weather ${weatherFor.weather.map{it.main}.joinToString(", ")}"
or use data classes
You need to overwrite WeatherData.toString() to have a hand on what's displayed.
class WeatherData(val main:String,val icon:String) {
override fun toString(): String {
return "$main $icon"
}
}
Further more you should use a RecyclerView with a ViewHolder to handle properties one-by-one and enable more complex layouts. If needed.