I am fetching JSON from URL using OKhttp...
I am Using Kotlin.
{
"CountryName": [{
"Country": "India",
"CountryCode": "+91",
"Population": "545121546846"
},
{
"country ": "Pakistan",
"countryCode": "+92",
"Population": "23546546546"
},
{
"Country": "UK",
"CountryCode": "+42",
"Population": "545121546846"
},
{
"Country": "US",
"CountryCode": "+1",
"Population": "54512154545846"
}
]
}
When successfully fetched data its show only when user search Country and add to a list and show its related data like countryside and population. I am able to fetch JSON data, But unable to control it like when user search and add to List. This is my main Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView.layoutManager = LinearLayoutManager(this)
fetchjson()
}
//This is How I fetch JSON
fun fetchjson() {
val url = "https://firebasestorage.googleapis.com/v0/b/unity-b69ff.appspot.com/o/new.json?alt=media&token=dc24acb2-13aa-40da-b0c5-7bb3ccd59af8"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
println("Fail to load json")
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
println(body)
val gson = GsonBuilder().create()
val homefeed = gson.fromJson(body, HomeFeed::class.java)
runOnUiThread{
recyclerView.adapter = MainAdapter(homefeed)
}
}
})
}
}
This Is my MainAdapter
class adapter(val homefeed: jjson) : RecyclerView.Adapter
<adapter.CustomViewHolder>(), Filterable {
override fun getItemCount(): Int {
return homefeed.CountryName.count()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cellforRow = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
return CustomViewHolder(cellforRow)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val v = homefeed.CountryName.get(position)
holder.itemView.textView2.text = v.Country
holder.itemView.textView.text = v.CountryCode.toString()
}
class CustomViewHolder(view: View) : RecyclerView.ViewHolder(view) {}
// this filter method
override fun getFilter(): Filter {
return (object: Filter(){
override fun performFiltering(constraint: CharSequence?): FilterResults {
// TODO("Not yet implemented")
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
// TODO("Not yet implemented")
}
})
}
}
//json data object
class jjson(val CountryName: List<name>)
class name{
val Country: String? = null
val CountryCode: Int? = null
val Popuplation: Long? = null
}
CAN I EXECUTE SOME CODE WRITTEN IN JSON WHILE AFTER FETCHING
Now that I understand a little better your problem, I'm writing an answer.
Remember that your adapter only takes care of displaying a set of data, in this case the list of countries you want the user to see. Currently, you're just initializing the adapter with the whole list of countries once you receive the response from the server.
That you could do is initialize the adapter with an empty list, update the adapter dataset when the countries are fetched from the server and then update it again everytime the user search query changes.
You may need to use a different type of adapter (an ArrayAdapter for example), to make it easier to work with your list data. You can read more about it here: https://developer.android.com/guide/topics/ui/declaring-layout#AdapterViews
Once you have your adapter set up, what you need to do is:
1. Initialize it with an empty list, it can be done on its constructor.
2. Update its dataset when the countries come from the server:
adapter.clear()
adapter.addAll(countries)
adapter.notifyDataSetChanged()
Update its dataset when the user makes a search:
adapter.clear()
adapter.addAll(yourFilteredCountries) // you can keep the whole list of countries in a variable in the activity and just pass a filtered list here
adapter.notifyDataSetChanged()
Note that this is a super simple approach and you can improve it in many ways, but it will help you understand how adapters work.
Let me know how it goes
Related
I'm trying to fetch multiple documents from my Firestore collection so I can populate my RecyclerView. However, I'm getting a mismatch error when I try to hook my categories ArrayList to the QuerySnapshot, it says it's looking for kotlin.collections.ArrayList<Category> but it found Category?. What can I do to make my RecyclerView populate my category collection in Firestore? Do I need to rewrite my val categories = ArrayList<Category>()? Thank you!
Category Collection
Category.kt
data class Category(var category: String?, val categoryImage: String?) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(category)
parcel.writeString(categoryImage)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Category> {
override fun createFromParcel(parcel: Parcel): Category {
return Category(parcel)
}
override fun newArray(size: Int): Array<Category?> {
return arrayOfNulls(size)
}
}
}
CategoryAdapter.kt
class CategoryAdapter(val category: ArrayList<Category>) : RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {
var selectedCategory = Category("", "")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindCategory(category[position])
holder.itemView.setOnClickListener { v ->
val context: Context = v.context
val intent = Intent(context, CategoryServiceActivity::class.java)
selectedCategory.category = category[position].category
intent.putExtra("category", selectedCategory)
context.startActivity(intent)
}
}
override fun getItemCount(): Int {
return category.count()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.categoryrecyclyerview, parent, false)
return ViewHolder(view)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val categoryName = itemView.findViewById<TextView>(R.id.categoryJobNameTextView)
val categoryImage = itemView.findViewById<ImageView>(R.id.categoryImageView)
fun bindCategory(category: Category) {
categoryName?.text = category.category
Picasso.get().load(category.categoryImage).into(categoryImage)
}
}
}
HomeFragment.kt
val categories = ArrayList<Category>()
val categoriesDatabaseRef = FirebaseFirestore.getInstance().collection(REF_JOB_CATEGORIES)
categoriesDatabaseRef.orderBy("category").get().addOnSuccessListener(object: OnSuccessListener<QuerySnapshot> {
override fun onSuccess(p0: QuerySnapshot?) {
if (p0 != null) {
for (querySnapshot in p0.documents) {
categories = querySnapshot.toObject(Category::class.java)
}
}
}
})
As I see in your code, the categories object is an ArrayList. So when you're using the following line of code:
categories = querySnapshot.toObject(Category::class.java)
It means that you're trying to convert the querySnapshot object, which is actually a DocumentSnapshot object, into an object of type Category, which works perfectly fine. However, you cannot assign that value to the categories object because between the ArrayList and Category classes, there is no inheritance relationship, hence that error.
So there are two ways in which you can solve this. The first solution would be to add an object of type Category, at each iteration of the for loop to the list:
categoriesDatabaseRef.orderBy("category").get().addOnSuccessListener(object: OnSuccessListener<QuerySnapshot> {
override fun onSuccess(p0: QuerySnapshot?) {
if (p0 != null) {
for (querySnapshot in p0.documents) {
val category = querySnapshot.toObject(Category::class.java)
categories.add(category) //Add the object to the list.
}
}
}
})
The second solution, which is even simpler in my opinion, would be to convert the querySnapshot directly into a list, by removing the for loop like this:
categoriesDatabaseRef.orderBy("category").get().addOnSuccessListener(object: OnSuccessListener<QuerySnapshot> {
override fun onSuccess(p0: QuerySnapshot?) {
if (p0 != null) {
categories = p0.toObjects(Category::class.java)
}
}
})
Please see that I have used toObjects(Class clazz) method which:
Returns the contents of the documents in the QuerySnapshot, converted to the provided class, as a list.
So it's toObjects, see the s? And not toObject.
Besides that, don't forget that Firebase API is asynchronous. So you cannot simply use the value of categories outside the onSuccess() method. If you're new to asynchronous programming, I recommend you read the following resource:
How to read data from Cloud Firestore using get()?
Currently, I am making a task in Android that changes the unit value of the list according to the toggle button and shows the list with the changed value.
I am observing the list using a ViewModel and LiveData.
So i use toList() to return a new list and overwrite the old list to observe the values.
However, the screen is not updated even though it has returned a new list.
I've tried debugging and I'm getting some incomprehensible results.
Obviously, the address values of the old list and the new list are different, but even the unit of the old list has changed.
What happened?
Even if the addresses of Lists are different, do the values of the old list and the new list change at the same time because the properties refer to the same place?
I'll show you the minimal code.
Fragment
// Change Unit
toggleButton.addOnButtonCheckedListener { _, checkedId, isChecked ->
if(isChecked) {
when(checkedId) {
R.id.kg -> vm.changeUnit("kg")
R.id.lb -> vm.changeUnit("lbs")
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
vm.items.observe(viewLifecycleOwner) { newList ->
adapter.submitList(newList)
}
}
WorkoutSetInfo
#Entity(
foreignKeys = [
ForeignKey(
entity = Workout::class,
parentColumns = arrayOf("workoutId"),
childColumns = arrayOf("parentWorkoutId"),
onDelete = ForeignKey.CASCADE
)
]
)
data class WorkoutSetInfo(
#PrimaryKey(autoGenerate = true)
val id: Long = 0,
val set: Int,
var weight: String = "",
var reps: String = "",
var unit: String = "kg",
val parentWorkoutId: Long = 0
)
Adapter
class DetailAdapter
: ListAdapter<WorkoutSetInfo, DetailAdapter.ViewHolder>(DetailDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemRoutineDetailBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(currentList[position])
}
inner class ViewHolder(val binding: ItemRoutineDetailBinding) : RecyclerView.ViewHolder(binding.root) {
private var weightTextWatcher: TextWatcher? = null
private var repTextWatcher: TextWatcher? = null
fun bind(item: WorkoutSetInfo) {
binding.set.text = item.set.toString()
binding.weight.removeTextChangedListener(weightTextWatcher)
binding.unit.text = item.unit
binding.rep.removeTextChangedListener(repTextWatcher)
weightTextWatcher = object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { }
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { }
override fun afterTextChanged(w: Editable?) {
if(!binding.weight.hasFocus())
return
item.weight = w.toString()
}
}
repTextWatcher = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }
override fun afterTextChanged(r: Editable?) {
if(!binding.rep.hasFocus())
return
item.reps = r.toString()
}
}
binding.apply {
weight.setTextIfDifferent(item.weight)
weight.addTextChangedListener(weightTextWatcher)
rep.setTextIfDifferent(item.reps)
rep.addTextChangedListener(repTextWatcher)
}
}
}
}
DiffUtil*
class DetailDiffCallback : DiffUtil.ItemCallback<WorkoutSetInfo>() {
override fun areItemsTheSame(
oldItem: WorkoutSetInfo,
newItem: WorkoutSetInfo
): Boolean {
return (oldItem.id == newItem.id)
}
override fun areContentsTheSame(
oldItem: WorkoutSetInfo,
newItem: WorkoutSetInfo
): Boolean {
return oldItem == newItem
}
}
ViewModel
class DetailViewModel(application: Application, title: String) : ViewModel() {
private val workoutDao = DetailDatabase.getDatabase(application)!!.workoutDao()
private val repository: WorkoutRepository = WorkoutRepository(workoutDao, title)
private val _items: MutableLiveData<List<WorkoutSetInfo>> = MutableLiveData()
val items = _items
fun changeUnit(unit: String) {
repository.changeUnit(unit)
_items.postValue(repository.getList())
}
fun addSet() {
viewModelScope.launch(Dispatchers.IO){
repository.add()
_items.postValue(repository.getList())
}
}
fun deleteSet() {
repository.delete()
_items.postValue(repository.getList())
}
fun save() {
viewModelScope.launch(Dispatchers.IO) {
repository.save()
}
}
}
Repository
class WorkoutRepository(private val workoutDao : WorkoutDao, title: String) {
private val workout = Workout(title = title)
private val setInfoList = ArrayList<WorkoutSetInfo>()
fun changeUnit(unit: String) {
setInfoList.map { setInfo ->
setInfo.unit = unit
}
}
fun add() {
val item = WorkoutSetInfo(set = setInfoList.size + 1)
setInfoList.add(item)
}
fun delete() {
if(setInfoList.size != 0)
setInfoList.removeLast()
return
}
fun save() {
val workoutId = workoutDao.insertWorkout(workout)
val newWorkoutSetInfoList = setInfoList.map { setInfo ->
setInfo.copy(parentWorkoutId = workoutId)
}
workoutDao.insertSetInfoList(newWorkoutSetInfoList)
}
fun getList() : List<WorkoutSetInfo> = setInfoList.toList()
}
You'd need to post your observer code for any help with why it's not updating.
As for the weird behaviour, setInfoList contains a few WorkoutSetInfo objects, right? Let's call them A, B and C. When you call setInfoList.toList() you're creating a new container, which holds the same references to objects A, B and C. Because it's a separate list, you can add and remove items without affecting the original list, but any changes to the objects that both share will be reflected in both lists - because they're both looking at the same thing.
So when you do setInfoList.map { setInfo -> setInfo.unit = unit } (which should be forEach really, map creates a new list you're discarding) you're modifying A, B and C. So every list you've made that contains those objects will see those changes, including your old list.
Basically if you want each list to be independent, when you modify the list you need to create new instances of the items, which means copying your WorkoutSetInfo objects to create new ones, instead of updating the current ones. If it's a data class then you can do that fairly easily (so long as you don't have nested objects that need copying themselves):
// var so we can replace it with a new list
private var setInfoList = listOf<WorkoutSetInfo>()
fun changeUnit(unit: String) {
// create a new list, copying each item with a change to the unit property
setInfoList = setInfoList.map { setInfo ->
setInfo.copy(unit = unit)
}
}
You don't need to do toList() on getList anymore, since you're just passing the current version of the list, and that list will never change (because you'll just create a new one). Meaning you don't need that function, you can just make setInfoList public - and because I changed it to listOf which creates an immutable List, it's safe to pass around because it can't be modified.
The WorkoutSetInfo objects inside that list could still be modified externally though (e.g. by changing one of the items' unit value), so instead of making a new copy when you call changeUnit, you might want to do it when you call getList instead:
class WorkoutRepository(private val workoutDao : WorkoutDao, title: String) {
private val workout = Workout(title = title)
private val setInfoList = ArrayList<WorkoutSetInfo>()
// store the current unit here
private var currentUnit = "kg"
fun changeUnit(unit: String) {
currentUnit = unit
}
// return new List
fun getList() : List<WorkoutSetInfo> = setInfoList.map { it.copy(unit = currentUnit) }
}
Now everything that calls getList gets a unique list with unique objects, so they're all separate from each other. And if you don't actually need to store the current unit value, you could pass it in to getList instead of having a changeUnit function:
fun getList(unit: String) = setInfoList.map { it.copy(unit = unit) }
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.
I am creating one Android app and trying to set the data in Recyclerview, I am using MVVM architecture pattern with kotlin, I can see data in logcat but when app loads I am not seeing any data in my recyclerview. Following is my code.
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var productViewModel: ProductViewModel
private lateinit var binding: ActivityMainBinding
val adapter = ProductAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val productService = RetrofitHelper.getInstance().create(ProductService::class.java)
val productRepository = ProductRepository(productService)
productViewModel = ViewModelProvider(this, ProductViewModelFactory(productRepository)).get(ProductViewModel::class.java)
binding.recyclerview.adapter = adapter
productViewModel.products.observe(this,{
Log.d("TEST",it.toString())
adapter.notifyDataSetChanged()
})
}
}
ProductAdapter
class ProductAdapter : RecyclerView.Adapter<ProductViewHolder>() {
var movies = mutableListOf<MobileList>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = AdapterLayoutBinding.inflate(inflater, parent, false)
return ProductViewHolder(binding)
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
val movie = movies[position]
holder.binding.name.text = movie.products.get(position).name
Glide.with(holder.itemView.context).load(movie.products.get(position).image_url).into(holder.binding.imageview)
}
override fun getItemCount(): Int {
return movies.size
}
}
class ProductViewHolder(val binding: AdapterLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
}
Repository class
class ProductRepository (private val productService: ProductService) {
private val productLiveData = MutableLiveData<MobileList>()
val products:LiveData<MobileList>
get() = productLiveData
suspend fun getProducts(){
val products = productService.getQuotes()
if(products?.body()!=null)
{
productLiveData.postValue(products.body())
}
}
}
ViewModel
class ProductViewModel (private val productRepository: ProductRepository ) :ViewModel() {
init {
viewModelScope.launch(Dispatchers.IO){
productRepository.getProducts()
}
}
val products : LiveData<MobileList>
get() = productRepository.products
}
Factory
class ProductViewModelFactory (private val productRepository: ProductRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ProductViewModel (productRepository) as T
}
}
Model
data class MobileList(
val products: List<Product>
)
data class Product(
val image_url: String,
val name: String,
val price: String,
val rating: Int
)
JSON Response
{
"products": [
{
"name": "test",
"price": "34999",
"image_url": "url",
"rating": 4
},
{
"name": "test2",
"price": "999",
"image_url": "url",
"rating": 4
},]}
First of all make sure you have layoutManager set on the RecyclerView.
The problem here is Your ProductAdapter never had the data . notifyDataSetChanged is not a magic stick to notify the adapter you modify/add/update the dataset and then You will call notifyDataSetChanged . that's how it works .
In your case You have movies list your adapter but you never assigned anything to it its always empty .
There are several ways to it. Just to make it work You can have a method to add the data in your adapter and then notify it.
fun addData(data:List<MobileList>){
movies.addAll(data)
notifyDataSetChanged()
}
Now when you get the data inside on change you call this method .
productViewModel.products.observe(this,{
it?.let{ items ->
adapter.addData(items)
}
})
This should work .
Update on type fix - Seems like your type is messed up . Why is your repo returns a object of MobileList? While you are using a list of MobileList in adapter . Your adapter should hold var movies = mutableListOf<Products>().
productViewModel.products.observe(this,{
it?.let{ item ->
adapter.addData(item.products)
}
})
I have a kotlin app of ListView of items from firebase.
The app can insert items to the firebase and everything works great with simple array adapter.
BUT what if I need to do that without using simple array adapter?
In the data class I have toString() and I need to use it without the simple adapter
data class Product(var name:String, var quantity: Int, var size: String, var comment:String){
private var _ky:String = ""
constructor() : this("", 0, "", "")
override fun toString(): String {
return "%s, %d, %s\n%s".format(this.name,this.quantity,this.size,this.comment)
}
fun fillKey(ky:String) {
this._ky = ky
}
fun giveKey():String {
return this._ky
}
}
In the activity I have this
val products = ArrayList<Product>()
val adptr: ArrayAdapter<Product> by lazy {ArrayAdapter<Product>(this, android.R.layout.simple_list_item_checked, products)}
But I don't know what to do with this if I don't use an adapter
lvItems.adapter = adptr
lvItems.choiceMode = ListView.CHOICE_MODE_MULTIPLE
myRef.child("products").addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
val p: Product = snapshot.getValue(Product::class.java) as Product
p.fillKey(snapshot.key!!)
products.add(p)
adptr.notifyDataSetChanged()
lvItems.smoothScrollToPosition(products.size - 1)
I need to do make it work without an adapter because of my mentor decision.