KOTLIN: Basic Async / Coroutines - android

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
}
)

Related

How can I show data from api which has single data only?

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.

Get clicked item in recyclerview adapter

I want to create an android app with Kotlin. In this app, i use swagger also to get all the web service in a file.
I want to create an interface, the description is as follows:
A RecyclerView horizontal that contains all the list of categories
comes from a web service apiMobileProductCategoriesGetAllPost.
after that, when i click on a which category, a RecyclerView(Grid)
appear that contains all the product by category id.
I want to know how can i get the category id when i click on item,and how to use it in the activity
The following the RecyclerView category adapter:
class CategoryAdapter(private val categories: Array<ProductCategoryData>) :
RecyclerView.Adapter<CategoryAdapter.ViewHolder>(), View.OnClickListener {
private var onItemClickListener: OnItemClickListener? = null
override fun onClick(v: View?) {
if (v != null) {
onItemClickListener?.onItemClick(v, ProductCategoryData())
}
}
fun setOnItemClickListener(onItemClickListener: OnItemClickListener) {
this.onItemClickListener = onItemClickListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.category_item, parent, false)
view.setOnClickListener(this)
return ViewHolder(view)
}
override fun getItemCount() = categories.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itemCategory: ProductCategoryData = categories[position]
holder.categoryId.text = itemCategory.id.toString()
println(holder.categoryId.text)
println(itemCategory.name?.get("En").toString())
holder.categoryName.text = itemCategory.name?.get("En").toString()
println(itemCategory.id)
if (itemCategory.logo != null) {
Picasso.get()
.load("..../T/${itemCategory.logo}")
.into(holder.categoryImage, object : com.squareup.picasso.Callback {
override fun onError(e: Exception?) {
holder.categoryImage.setImageResource(R.drawable.homecraftdefault)
}
override fun onSuccess() {
Picasso.get().load("....T/${itemCategory.logo}")
.into(holder.categoryImage)
}
})
holder.itemView.setOnClickListener {
onItemClickListener?.onItemClick(holder.itemView,itemCategory)
}
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
val categoryName: TextView = itemView.categoryName
val categoryImage: ImageView = itemView.categoryImage
val categoryId: TextView = itemView.categoryId
override fun onClick(v: View?) {
if (v != null) {
onItemClickListener?.onItemClick(v, ProductCategoryData())
}
}
}
interface OnItemClickListener {
fun onItemClick(view : View, viewModel:ProductCategoryData)
}
}
The following code is relative to the activity:
class CategoryByProduct : AppCompatActivity(), CategoryAdapter.OnItemClickListener {
override fun onItemClick(view: View, viewModel: ProductCategoryData) {
var params = "CategoryProductID";"5cc057458c4d9823743736d2"
println(viewModel.id)
val products = mobileApi!!.apiMobileProductsGetAllPost(params, 0, 50, "", "")
recyclerViewProductByCategory.apply {
recyclerViewProductByCategory.layoutManager = GridLayoutManager(this#CategoryByProduct, 2)
recyclerViewProductByCategory.adapter = ProductAdapter(products)
} }
var mobileApi: MobileApi? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.list_product_by_category)
mobileApi = MobileApi()
val params = HashMap<String, String>()
GlobalScope.launch(Dispatchers.IO) {
val categories = mobileApi!!.apiMobileProductCategoriesGetAllPost(params, 0, 50, "", "")
withContext(Dispatchers.Main) {
recyclerViewCategories.apply {
recyclerViewCategories.layoutManager =
LinearLayoutManager(this#CategoryByProduct, OrientationHelper.HORIZONTAL, false)
recyclerViewCategories.adapter = CategoryAdapter(categories)
}
}
}
}
}
First of all , never put your onclick in onBindViewHolder That's not a good practice, after that i think you need to get the ID of the category i will give you simple example in all of the Adapter Class
class NewsAdapter (val context: Context, private val arrayList: ArrayList <NewsModel>):
RecyclerView.Adapter <NewsAdapter.Holder> () {
companion object {
// val TAG: String = OperationAdapter::class.java.simpleName
}
override fun onCreateViewHolder (parent: ViewGroup, viewType: Int): Holder {
return Holder (LayoutInflater.from (parent.context ).inflate (R.layout.newslist , parent, false))
}
override fun getItemCount (): Int = arrayList. size
override fun onBindViewHolder (holder: Holder, position: Int) {
val mynews = arrayList[position]
holder.setData(mynews , position)
}
inner class Holder (itemView: View): RecyclerView.ViewHolder (itemView) {
private var currentnews: NewsModel? = null
private var currentPosition: Int = 0
init {
//The click listener
itemView.newscardview.setOnClickListener {
//do it here
Toast.makeText(this,currentnews!!.id,Toast.LENGTH_SHORT).show()
}
//the end of the init
}
//getting data from model and bind it into View
fun setData(news: NewsModel?, position: Int) {
news?.let {
itemView.newsid.text = news.id
itemView.newstitle.text = news.title
itemView.body.text = news.body
itemView.txtdate.text = news.ndate
}
this.currentnews = news
this.currentPosition = position
}
}
}
In this example you will get the news ID when you click newscardview, i hope to understand it
In your Activity
put this code in onCreate
//set up the recycleview
mRecyclerView.setHasFixedSize (true)
mRecyclerView. layoutManager = LinearLayoutManager(this)
mRecyclerView is my RecycleView
also call your Adapter class in anywhere you want
//adapter
val adapter = NewsAdapter (this,arrayList)
adapter.notifyDataSetChanged()
mRecyclerView.adapter = adapter
you get the position of the category inside a viewholder by calling adapterPosition and with this you can get the category from your list you provide to your adapter in the constructor (categories[adapterPosition])
In your case it is very simple.Try these:-
holder.tv_broadcast_title.text=broadList[position].name
where broadlist is my array list created in the adapter itself.In this list the json data is getting stored from api.
internal var broadList = ArrayList<Model>()
and .name is the name of key to fetch name from json data.
holder.categoryName.text = itemCategory.name?.get("En").toString()
in your case do something like this:-
itemCategory[position].name
To get data from adapter to activity, you can make an interface in the adapter or globally and from the activity you can pass that interface in adapter's constructor and use that to get data. I am giving you an example.
interface ProductCategoryListner {
fun getProductCategory(viewModel:ProductCategoryData)
}
Not in adapter's constructor add this interface.
class CategoryAdapter(private val categories: Array<ProductCategoryData>,private val productCategoryListner: ProductCategoryListner):
RecyclerView.Adapter<CategoryAdapter.ViewHolder>(), View.OnClickListener {
Now you can use this to pass data in the activity when you click on view.
productCategoryListner.getProductCategory(categories[adapterPosition])

Button Checklist all on Recyclerview does not work

I have recyclerview with checkbox and I want to checklist all the data using button. I have trying this tutorial, but when i click the button, the log is call the isSelectedAll function but can't make the checkbox checked. what wrong with my code?
this is my adapter code
var isSelectedAll = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListApproveDeatilViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.activity_list_approve_row, parent, false)
return ListApproveDeatilViewHolder(itemView)
}
private lateinit var mSelectedItemsIds: SparseBooleanArray
fun selectAll() {
Log.e("onClickSelectAll", "yes")
isSelectedAll = true
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: ListApproveDeatilViewHolder, position: Int) {
val approve = dataSet!![position]
holder.soal.text = approve.title
holder.kategori.text = approve.kategori
if (!isSelectedAll){
holder.checkBox.setChecked(false)
} else {
holder.checkBox.setChecked(true)
}
}
and this is my activity code
override fun onCreate(savedInstanceState: Bundle?) {
private var adapter: ListApproveDetailAdapter? = null
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list_approve)
ButterKnife.bind(this)
getData()
// this is my button onclick code
select.setOnClickListener(){
if (select.getText().toString().equals("Select all")){
Toast.makeText(this, "" + select.getText().toString(), Toast.LENGTH_SHORT).show()
adapter?.selectAll()
select.setText("Deselect all")
} else {
Toast.makeText(this, "" + select.getText().toString(), Toast.LENGTH_SHORT).show()
select.setText("Select all")
}
}
}
//this is for get my data for the recyclerview
fun getData() {
val created_by = intent.getStringExtra(ID_SA)
val tgl_supervisi = intent.getStringExtra(TGL_SURVEY)
val no_dlr = intent.getStringExtra(NO_DLR)
API.getListApproveDetail(created_by, tgl_supervisi, no_dlr).enqueue(object : Callback<ArrayList<ListApprove>> {
override fun onResponse(call: Call<ArrayList<ListApprove>>, response: Response<ArrayList<ListApprove>>) {
if (response.code() == 200) {
tempDatas = response.body()
Log.i("Data Index History", "" + tempDatas)
recyclerviewApprove?.setHasFixedSize(true)
recyclerviewApprove?.layoutManager = LinearLayoutManager(this#ListApproveActivity)
recyclerviewApprove?.adapter = ListApproveDetailAdapter(tempDatas)
adapter?.notifyDataSetChanged()
} else {
Toast.makeText(this#ListApproveActivity, "Error", Toast.LENGTH_LONG).show()
}
swipeRefreshLayout.isRefreshing = false
}
override fun onFailure(call: Call<ArrayList<ListApprove>>, t: Throwable) {
Toast.makeText(this#ListApproveActivity, "Error", Toast.LENGTH_SHORT).show()
swipeRefreshLayout.isRefreshing = false
}
})
}
thankyou for any help :)
I am posting the answer with implementation of demo project. I haven't modified your code but as per your requirement i have done this.
MainActivity class:
class MainActivity : AppCompatActivity() {
var selectAll: Boolean = false;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) as RecyclerView
val btnSelectAll = findViewById<Button>(R.id.btnSelectAll) as Button
//adding a layoutmanager
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayout.VERTICAL, false)
//crating an arraylist to store users using the data class user
val users = ArrayList<User>()
//adding some dummy data to the list
users.add(User("Piyush", "Ranchi"))
users.add(User("Mehul", "Chennai"))
users.add(User("Karan", "TamilNadu"))
users.add(User("Bela", "Kolkata"))
//creating our adapter
val adapter = CustomAdapter(users, selectAll)
//now adding the adapter to recyclerview
recyclerView.adapter = adapter
btnSelectAll.setOnClickListener {
if (!selectAll) {
selectAll = true
} else {
selectAll = false
}
adapter?.selectAllCheckBoxes(selectAll)
}
}
}
User class:
data class User(val name: String, val address: String)
Adapter class:
class CustomAdapter(val userList: ArrayList<User>, val selectAll: Boolean) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
var selectAllA = selectAll;
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_layout, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: CustomAdapter.ViewHolder, position: Int) {
holder.textViewName.text = userList[position].name;
if (!selectAllA){
holder.checkBox.setChecked(false)
} else {
holder.checkBox.setChecked(true)
}
}
//this method is giving the size of the list
override fun getItemCount(): Int {
return userList.size
}
//the class is hodling the list view
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textViewName = itemView.findViewById(R.id.textViewUsername) as TextView
val checkBox = itemView.findViewById(R.id.checkbox) as CheckBox
}
fun selectAllCheckBoxes(selectAll: Boolean) {
selectAllA = selectAll
notifyDataSetChanged()
}
}
As i already mentioned in comments you are using two different adapter instance .
Now i see you have declared adapter globally .
Just modify your code as follows and make sure response.body() have data int it :
if (response.code() == 200) {
tempDatas = response.body()
Log.i("Data Index History", "" + tempDatas)
recyclerviewApprove?.setHasFixedSize(true)
recyclerviewApprove?.layoutManager = LinearLayoutManager(this#ListApproveActivity)
adapter = ListApproveDetailAdapter(tempDatas)
recyclerviewApprove?.adapter=adapter
} else {
Toast.makeText(this#ListApproveActivity, "Error", Toast.LENGTH_LONG).show()
}
Add one variable in model class.
like var isSelect : Boolean
In your selectAll() method update adpter list and notify adapter.
Edit:
in the adapter class.
if (approve.isSelect){
holder.checkBox.setChecked(true)
} else {
holder.checkBox.setChecked(false)
}
Hope this may help you.
OR
If you are using AndroidX then use should use one recyclerview features.
androidx.recyclerview.selection
A RecyclerView addon library providing support for item selection. The
library provides support for both touch and mouse driven selection.
Developers retain control over the visual representation, and the
policies controlling selection behavior (like which items are eligible
for selection, and how many items can be selected.)
Reference from here

How to fetch data from JSON in Kotlin Android

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.

BoundaryCallback in PageList never gets called

I'm using Paging Library with PagedListAdapter, I'm using BoundaryCallback in PagedList to know when the user reaches the end of the list. The problem is that the method in my callback never gets called.
This is my data source code:
class PropertyDataSource : ItemKeyedDataSource<Int, Property>() {
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Property>) {
callback.onResult(getProperties(1, params.requestedLoadSize))
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Property>) {
Thread.sleep(1000)
callback.onResult(getProperties(params.key + 1, params.requestedLoadSize + params.key))
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Property>) {
}
override fun getKey(item: Property): Int {
return item.id
}
}
my code for boundary callback is
class MyBoundryCallBack: PagedList.BoundaryCallback<Property>() {
override fun onItemAtEndLoaded(itemAtEnd: Property) {
Log.e("alz", "at end $itemAtEnd")
}
override fun onItemAtFrontLoaded(itemAtFront: Property) {
Log.e("alz", "at front $itemAtFront")
}
override fun onZeroItemsLoaded() {
Log.e("alz", "zero item loaded")
}
}
here is my activity code
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = MyListAdapter(PropertyDiffCallback())
mainRecyclerView.adapter = adapter
mainRecyclerView.layoutManager = LinearLayoutManager(this)
val propertyDataSourceFactory = PropertyDataSourceFactory()
val config = PagedList.Config.Builder()
.setPageSize(2)
.setInitialLoadSizeHint(3)
.setEnablePlaceholders(false)
.build()
val pagedList = LivePagedListBuilder(propertyDataSourceFactory, config)
.setBoundaryCallback(MyBoundryCallBack())
.build()
pagedList.observe(this, Observer {
adapter.submitList(it)
}
}
And my code for Adapter is look like this
class MyListAdapter(diffCallback: DiffUtil.ItemCallback<Property>) :
PagedListAdapter<Property, RecyclerView.ViewHolder>(diffCallback) {
class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val titleTV: TextView = itemView.findViewById(R.id.title)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ItemHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_property, parent, false))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if(getItem(position) == null) return
val itemHolder = holder as ItemHolder
itemHolder.titleTV.text = getItem(position)!!.title
}
}
And here is my implementation of getProperties() that will return some mock data. Later on, I'm going to change it to load data from the server.
fun getProperties(from: Int, to: Int): List<Property> {
val list = ArrayList<Property>()
for (i in from..to){
list.add(Property(i, "item $i"))
}
return list
}
Note that, my code works fine and my adapter requesting more data as it goes to the end, I just don't get the callback when PagedList gets to the end.

Categories

Resources