I can access my fun inside the ViewHolder from the adapter - function

I try to call fun bind declared in the inner class LaunchesViewHolder from onBindViewHolder() but I got error "Unresolved resource bind"
I was trying with an other variable x, just to see, same problem
class LaunchesAdapter(private val dataSet: List<LaunchItem>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
inner class LaunchesViewHolder( val binding: LaunchesItemLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
val x = 0
public fun bind(currentLaunch: LaunchItem) {
//do something
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return LaunchesViewHolder(
LaunchesItemLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(dataSet[position]) => error unresolved resource bind
holder.x =1 => error unresolved resource x
}
override fun getItemCount(): Int {
return dataSet.size
}
}````

In your onBindViewHolder you should use your specific ViewHolder, that is LaunchesViewHolder and not the RecyclerView.ViewHolder. Please see code below.
override fun onBindViewHolder(holder: LaunchesViewHolder, position: Int) {
holder.bind(dataSet[position])
}
Edited:
You need to specify the class you override too
class LaunchesAdapter(private val dataSet: List<LaunchItem>) :
RecyclerView.Adapter<LaunchesAdapter.LaunchesViewHolder>() {
}

it works with
(holder as LaunchesViewHolder).bind(dataSet[position])
instead of holder.bind(dataSet[position])
see more details https://www.section.io/engineering-education/implementing-multiple-viewholders-in-android-using-kotlin/

Related

com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException

I am facing such an error in my application. I guess the problem is due to having char in note_title and note_desc. I couldn't find the solution. Is there anyone who can help?
navgraph
error
NoteDetailScreen
notes entity
? and other char cause error
I tried change note_title and note_desc types but didnt work.
I solved this way;
Let’s say you have a class like this:
#Parcalize
#Entity(tableName = "NOTES")
data class Notes(
#PrimaryKey(autoGenerate = true)
#ColumnInfo("note_id") #NotNull var note_id:Int,
#ColumnInfo("note_title") #NotNull var note_title: String,
#ColumnInfo("note_desc") #NotNull var note_desc: String,
#ColumnInfo("note_date") #Nullable var note_date: String?
): Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString().toString(),
parcel.readString().toString(),
parcel.readString()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(note_id)
parcel.writeString(note_title)
parcel.writeString(note_desc)
parcel.writeString(note_date)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Notes> {
override fun createFromParcel(parcel: Parcel): Notes {
return Notes(parcel)
}
override fun newArray(size: Int): Array<Notes?> {
return arrayOfNulls(size)
}
}
}
annotation class Parcalize
You can define the NavType like this:
class NavTypo : NavType<Notes>(isNullableAllowed = false) {
override fun get(bundle: Bundle, key: String): Notes? {
return bundle.getParcelable(key)
}
override fun parseValue(value: String): Notes {
return Gson().fromJson(value, Notes::class.java)
}
override fun put(bundle: Bundle, key: String, value: Notes) {
bundle.putParcelable(key, value)
}
}
And use it:
composable(
"note_details_page/{note_id}",
arguments = listOf(
navArgument("note_id"){
type = NavTypo()
}
)
){
val note = it.arguments?.getParcelable<Notes>("note_id")
if (note != null) {
NoteDetailScreen(note, navController)
}
}
Card(
backgroundColor = choosedColor,
modifier = Modifier
.padding(3.dp)
.sizeIn(maxHeight = 250.dp)
.combinedClickable(onClick={
val note = allNotes.value!![it]
val noteJson = Uri.encode(Gson().toJson(note))
navController.navigate("note_details_page/${noteJson}")
}

Error parsing fragment to recyclerview in Kotlin

As part of one of my courses, I am using an API in Android Studio for the first time. For this I use OkHttp. However, I have the following error when I launch my application. I guess it's because the JSON is not parsed well but I can't find a solution.
If anyone could help me that would be great!
Thank you
The error screenshot
MyMarqueRecyclerViewAdapter.kt
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.google.gson.GsonBuilder
import kotlinx.android.synthetic.main.fragment_marque_list.*
import okhttp3.*
import java.io.IOException
/**
* A fragment representing a list of Items.
*/
class MarqueFragment : Fragment(), OnMarqueClickListener {
private var columnCount = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
columnCount = it.getInt(ARG_COLUMN_COUNT)
}
fetchJson()
}
fun fetchJson() {
val url = "https://tp3.infomobile.app/api/v1/brand"
val request = Request.Builder().url(url).build()
val lesmarques = OkHttpClient()
lesmarques.newCall(request).enqueue(object: Callback {
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)
println(homeFeed)
activity?.runOnUiThread {
recyclerView_main.adapter = MyMarqueRecyclerViewAdapter(homeFeed)
}
}
override fun onFailure(call: Call, e: IOException) {
println("Failed to execute request")
}
})
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_marque_list, container, false)
// Set the adapter
if (view is RecyclerView) {
with(view) {
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(context)
else -> GridLayoutManager(context, columnCount)
}
//adapter = MyMarqueRecyclerViewAdapter(homeFeed)
}
}
return view
}
override fun onMarqueItemClicked(position: Int) {
Toast.makeText(this.context, "ça marche", Toast.LENGTH_LONG).show()
val intent = Intent(this#MarqueFragment.requireContext(),MainActivity2::class.java)
startActivity(intent)
}
companion object {
// TODO: Customize parameter argument names
const val ARG_COLUMN_COUNT = "column-count"
// TODO: Customize parameter initialization
#JvmStatic
fun newInstance(columnCount: Int) =
MarqueFragment().apply {
arguments = Bundle().apply {
putInt(ARG_COLUMN_COUNT, columnCount)
}
}
}
}
class HomeFeed(val marques: List<Marque>)
class Marque(val id: Int, val name: String)
MarqueFragment.kt
import android.content.Intent
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import ca.ulaval.ima.tp3.placeholder.PlaceholderContent.PlaceholderItem
import ca.ulaval.ima.tp3.databinding.FragmentMarqueBinding
/**
* [RecyclerView.Adapter] that can display a [PlaceholderItem].
* TODO: Replace the implementation with code for your data type.
*/
class MyMarqueRecyclerViewAdapter(val homeFeed: HomeFeed?) : RecyclerView.Adapter<MyMarqueRecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
FragmentMarqueBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = homeFeed?.marques?.get(position)
holder.contentView.text = item.toString()
holder.contentView.setOnClickListener{
val context=holder.contentView.context
val intent = Intent( context, MainActivity2::class.java)
intent.putExtra("marque", item.toString())
context.startActivity(intent)
}
}
override fun getItemCount(): Int {
return homeFeed?.marques!!?.count()
}
inner class ViewHolder(binding: FragmentMarqueBinding) : RecyclerView.ViewHolder(binding.root) {
val contentView: TextView = binding.content
override fun toString(): String {
return super.toString() + " '" + contentView.text + "'"
}
}
}
First of all, you can trace the crash using the log which clearly states that the crash happened at line 42 in MyMarqueRecyclerViewAdapter.kt, in the getItemCount function, which means that this functions is causing it:
override fun getItemCount(): Int {
return homeFeed?.marques!!?.count()
}
What's happening here is that you're forcing kotlin to treat a property that might be null as a non null property, by using the "!!" operator, this tells kotlin that this propery would never be null in any case, and that isn't true in your case since its throwing a NullPointerException, what you actually need to do is allow it to be null and provide an alternative value in case it was null, using the elvis operator, like this:
override fun getItemCount(): Int {
return homeFeed?.marques?.count() ?: 0
}
Another thing is that you're using the count() function, i think you meant to use the size property instead:
override fun getItemCount(): Int {
return homeFeed?.marques?.size ?: 0
}
Second, i noticed your class declaration and compared the content to the json's response content, it seems that the json response conatins a parameter called "content", while the propery in the HomeFeed class is called "marques", which results in GSON not knowing where to get "marques" from, it only knows that there is a parameter called "content" in the json and it doesn't know what to do with it, the best solution would be to annotate the HomeFeed.marques property with a #SerializedName annotation, and provide the corresponding json parameter that should be mapped to this property, like so:
class HomeFeed(
#SerializedName("content")
val marques: List<Marque>
)
class Marque(
#SerializedName("id")
val id: Int,
#SerializedName("name")
val name: String
)
Hope this helps!
Some resources:
Null Safety | Kotlin
Difference between list.count() and list.size
Parsing between Kotlin classes and Json objects using GSON

Why apply() instead of function invokation

The following code does the same thing. The functions tr and td take a function literal with receiver object as input in order to add tr or td tag inside of a table.
class TABLE : Tag("table") {
fun tr(init: TR.() -> Unit) {
children += TR().apply(init)
}
}
class TR : Tag("tr") {
fun td(init: TD.() -> Unit) {
val td = TD()
td.init()
children += td
}
}
My Question is why do I need to use .apply() instead of:
class TABLE : Tag("table") {
fun tr(init: TR.() -> Unit) {
children += TR().init()
}
}
I guess it has something to do with the compiler looking for init() in the tr-object. But shouldn't this be decided on runtime?
As already suggested in my comment, using .apply you can chain invocations of init and += together, because apply returns the target of its invocation.
If you prefer to use init(), you can obtain the same result with
val tr = TR()
children += tr
tr.init()
The key aspect of the chained variation is that the applyfunction of the Kotlin's standard library is defined as an extension function of a generic typeT, accepting a *lambda with receiver as its sole parameter, as you can see here:
inline fun <T> T.apply(block: T.() -> Unit): T
In order to explain its meaning, you can implement this function yourself:
fun <T> T.myApply(block: T.() -> Unit) : T {
this.block()
return this
}
The following example mimics your code, using a fake MyClass type in place of the original TR:
fun <T> T.myApply(block: T.() -> Unit) : T {
this.block()
return this
}
class MyClass(val text: String) {
fun foo() : Unit {
println("foo $text")
}
}
fun initializer(mc: MyClass) {
println("initializer ${mc.text}")
mc.foo()
}
fun run(init: MyClass.() -> Unit) {
val result = MyClass("first").myApply(init)
val x = MyClass("second")
x.init()
}
fun main(args: Array<String>) {
run(::initializer)
}
You can play with this example in order to follow the flow from run to MyClass.foo, through the function accepting init as lambda with receiver parameter: I hope this can help you to clarify your understanding of the key charateristics of both the original and the alternative implementation of tr.

NotAMockException / How to stub a value in parameterized test in Kotlin?

For the following Kotlin class:
class ProductLogic(
private val product: Product?
) {
fun shouldShow(): Boolean {
if (product == null) {
return false
}
val version = product.version!!
if (!Utils.isAtLeastVersionX(version.major, version.minor)) {
return false
}
return true
}
}
I am trying to write a parameterized test in Kotlin:
#RunWith(ParameterizedRobolectricTestRunner::class)
#Config(constants = BuildConfig::class, sdk = [19], packageName = "com.example")
class ProductLogicTest(
private val product: Product?,
private val shouldShow: Boolean
) {
#Before
fun setUp() {
// doReturn(VERSION).`when`(product).version // (2) Raises a NotAMockException
}
#Test
fun shouldShow() {
assertThat(ProductLogic(product).shouldShow(), `is`(shouldShow))
}
companion object {
#JvmStatic
#Parameters(name = "{index}: {0} => {1}")
fun data(): Collection<Array<Any?>> {
val productMock = mock<Product>(Product::class.java)
doReturn(VERSION).`when`(productMock).version // (1) Is not applied
return asList(
arrayOf(productMock, false),
// ...
)
}
}
I want to parameterize the value of the Product#version property. When I (1) modify its value in the data() function it is not applied when running test. When I (2) try to modify its value in #Before a NotAMockException is raised:
org.mockito.exceptions.misusing.NotAMockException:
Argument passed to when() is not a mock!
Example of correct stubbing:
doThrow(new RuntimeException()).when(mock).someMethod();
Please note that the example is simplified - the real ProductLogic class consists of more parameters which rectify to using a parameterized test.
Robolectric and Mockito versions:
testImplementation 'org.robolectric:robolectric:4.1'
testImplementation 'org.mockito:mockito-core:2.23.4'
Also, to mock final classes, I created file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker with content:
mock-maker-inline
Classes to test:
class ProductLogic(private val product: Product?) {
fun shouldShow(): Boolean {
if (product == null) {
return false
}
val version = product.version
return !isAtLeastVersionX(version.minor, version.major)
}
private fun isAtLeastVersionX(minor: Int, major: Int): Boolean {
val v = 5
return v in minor..major
}
}
class Product(val version: Version)
class Version(val minor: Int, val major: Int)
Next test code works for me and test is passed:
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.robolectric.ParameterizedRobolectricTestRunner
import org.robolectric.annotation.Config
import java.util.Arrays.asList
#RunWith(ParameterizedRobolectricTestRunner::class)
#Config(sdk = [19], packageName = "com.example")
class ProductLogicTest(private val product: Product,
private val shouldShow: Boolean) {
#Before
fun setUp() {
//doReturn(VERSION).`when`(product).version // if uncomment works fine
}
#Test
fun shouldShow() {
assertThat(ProductLogic(product).shouldShow(), `is`(shouldShow))
}
companion object {
private val VERSION = Version(1, 5)
#JvmStatic
#ParameterizedRobolectricTestRunner.Parameters(name = "{index}: {0} => {1}")
fun data(): Collection<Array<Any?>> {
val productMock = mock(Product::class.java)
doReturn(VERSION).`when`(productMock).version // Works fine
return asList(
arrayOf(productMock, false)
)
}
}
}

retrofit + gson deserializer: return inside array

I have api that return json:
{"countries":[{"id":1,"name":"Australia"},{"id":2,"name":"Austria"}, ... ]}
I write model class (Kotlin lang)
data class Country(val id: Int, val name: String)
And I want do request using retorift that returning List < Models.Country >, from "countries" field in json
I write next:
interface DictService {
#GET("/json/countries")
public fun countries(): Observable<List<Models.Country>>
companion object {
fun create() : DictService {
val gsonBuilder = GsonBuilder()
val listType = object : TypeToken<List<Models.Country>>(){}.type
gsonBuilder.registerTypeAdapter(listType, CountriesDeserializer)
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
val service = Retrofit.Builder()
.baseUrl("...")
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
.build()
return service.create(DictService::class.java)
}
}
object CountriesDeserializer : JsonDeserializer<List<Models.Country>> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): List<Models.Country>? {
val res = ArrayList<Models.Country>()
if(json!=null) {
val countries = json.asJsonObject.get("countries")
if (countries.isJsonArray()) {
for (elem: JsonElement in countries.asJsonArray) {
res.add(Gson().fromJson(elem, Models.Country::class.java))
}
}
}
return null;
}
}
}
But I get error:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
CountriesDeserializer code dont execute even!
What they want from me?
Maybe I need write my own TypeAdapterFactory?
I dont want use model class like
class Countries {
public List<Country> countries;
}
If your intention is to simplify the interface and hide the intermediate wrapper object I guess the simplest thing to do is to add an extension method to the DictService like so:
interface DictService {
#GET("/json/countries")
fun _countries(): Observable<Countries>
}
fun DictService.countries() = _countries().map { it.countries }
data class Countries(val countries: List<Country> = listOf())
Which can then be used as follows:
val countries:Observable<List<Country>> = dictService.countries()
I found the way:
object CountriesTypeFactory : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson?, type: TypeToken<T>?): TypeAdapter<T>? {
val delegate = gson?.getDelegateAdapter(this, type)
val elementAdapter = gson?.getAdapter(JsonElement::class.java)
return object : TypeAdapter<T>() {
#Throws(IOException::class)
override fun write(outjs: JsonWriter, value: T) {
delegate?.write(outjs, value)
}
#Throws(IOException::class)
override fun read(injs: JsonReader): T {
var jsonElement = elementAdapter!!.read(injs)
if (jsonElement.isJsonObject) {
val jsonObject = jsonElement.asJsonObject
if (jsonObject.has("countries") && jsonObject.get("countries").isJsonArray) {
jsonElement = jsonObject.get("countries")
}
}
return delegate!!.fromJsonTree(jsonElement)
}
}.nullSafe()
}
}
But it is very complex decision, I think, for such problem.
Are there another one simpler way?
Another one:
I found bug in my initial code from start meassage!!!
It works fine if replace List by ArrayList!
I would use Jackson for this task.
Try this https://github.com/FasterXML/jackson-module-kotlin
val mapper = jacksonObjectMapper()
data class Country(val id: Int, val name: String)
// USAGE:
val country = mapper.readValue<Country>(jsonString)