I would like to pass objects (that I got through Retrofit) to fragments. I heard of 2 methods, but I have problems with both. I am trying to pass a FullForecast object to my fragments.
Use Parcelize. I implemented it on my class, but it conflicted with my constructor.
I heard I could pass a Json string from the activity to fragment and then convert it to an object once inside the fragment. However, I could not get the Json string from my Json call. I tried Call<ResponseBody>, and did response.body().toString(), but didnt get a Json string
Here is my code
repository.getWeatherForecast(place,
object : Callback<FullForecast> {
override fun onFailure(call: Call<FullForecast>?, t: Throwable?) {
println("onFailure")
}
override fun onResponse(call: Call<FullForecast>?, response: Response<FullForecast>?) {
if (response != null && response.isSuccessful && response.body() != null) {
forecastObj = response.body() as FullForecast
// Try to get Json string here
}
}
})
#JsonClass(generateAdapter = true)
data class FullForecast(#Json(name = "list")
val forecastList: List<WeatherForecast>) {
}
#JsonClass(generateAdapter = true)
data class WeatherForecast(#Json(name = "main")
val weatherDetail: WeatherDetail,
#Json(name = "weather")
val weatherIcon: List<WeatherIcon>,
#Json(name = "dt_txt")
val date: String) {
}
#JsonClass(generateAdapter = true)
data class Place(#Json(name = "main")
val weatherDetail: WeatherDetail,
#Json(name = "weather")
val weatherIcon: List<WeatherIcon>,
#Json(name = "sys")
val countryDetail: CountryDetail,
#Json(name = "dt_txt")
var forecastDate: String = "",
val name: String,
var placeIdentifier: String = "",
var lastUpdated: String = "") {
}
#JsonClass(generateAdapter = true)
data class CountryDetail(val country: String) {
}
#JsonClass(generateAdapter = true)
data class WeatherDetail(#Json(name = "temp")
val temperature: Double,
val temp_min: Double,
val temp_max: Double) {
}
#JsonClass(generateAdapter = true)
data class WeatherIcon(val icon: String) {
}
You should implement Parcelize as below
#Parcelize
#JsonClass(generateAdapter = true)
data class FullForecast(#Json(name = "list")
val forecastList: List<WeatherForecast>) : Parcelable {
}
#Parcelize
#JsonClass(generateAdapter = true)
data class WeatherForecast(#Json(name = "main")
val weatherDetail: WeatherDetail,
#Json(name = "weather")
val weatherIcon: List<WeatherIcon>,
#Json(name = "dt_txt")
val date: String) : Parcelable {
}
#Parcelize
#JsonClass(generateAdapter = true)
data class Place(#Json(name = "main")
val weatherDetail: WeatherDetail,
#Json(name = "weather")
val weatherIcon: List<WeatherIcon>,
#Json(name = "sys")
val countryDetail: CountryDetail,
#Json(name = "dt_txt")
var forecastDate: String = "",
val name: String,
var placeIdentifier: String = "",
var lastUpdated: String = "") : Parcelable {
}
#Parcelize
#JsonClass(generateAdapter = true)
data class CountryDetail(val country: String) : Parcelable {
}
#Parcelize
#JsonClass(generateAdapter = true)
data class WeatherDetail(#Json(name = "temp")
val temperature: Double,
val temp_min: Double,
val temp_max: Double) : Parcelable {
}
#Parcelize
#JsonClass(generateAdapter = true)
data class WeatherIcon(val icon: String) : Parcelable {
}
If you face error:
This is a known bug in the IDE itself and you can ignore it, there’s nothing wrong with the code and it works as expected. You can keep track of the issue here.
Try EventBus to pass objects to fragments.
Follow this
Step 1:
make your object implements Serializable
ex.public class Match implements Serializable {
}
step 2: put your object to bundle and pass it to Activity or Fragment
ex.
Bundle args = new Bundle();
args.putSerializable("ARG_PARAM1", yourObject);
step 3: get your object form bundle like this
yourObj = (Match) getArguments().getSerializable(ARG_PARAM1);
Related
I'm trying to make a post with retrofit and moshi but keep getting the error mess
com.squareup.moshi.JsonDataException: Expected BEGIN_OBJECT but was STRING at path $
I can't seem to understand why this is so. This is a sample of the json tested on postman:
{
"customerName": "Name",
"customerPhoneNo": "090000000",
"customerAddress": "Address",
"note": "Please",
"items" : [{
"productUid": "5633e1f1-8b00-46de-b73e-43799245a4e8",
"quantity" : "3"
},{
"ProductUid": "fca3ffb1-0130-4e47-b499-721d046c1e32",
"Quantity" : "5"
},
{
"ProductUid": "6a7f3e24-03ff-408a-b67e-8530d411390c",
"Quantity" : "2"
}]
}
My data classes are set up like so:
#Parcelize
data class Order(
val items: List<Item>?,
val customerName: String,
val customerPhoneNo: String,
val customerAddress: String,
val note: String
) : Parcelable
and
#Parcelize
data class Item(
val productUid: String,
var quantity: Int
) : Parcelable
Service utils look like:
interface ProductService {
#Headers("Content-Type: application/json")
#POST("/api/order/saveorder")
suspend fun postProds(#Body order: Order
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
object Network {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi)
.asLenient()
)
.build()
object ProdsApi {
val retrofitService: ProductService by lazy {
retrofit.create(ProductService::class.java)
}
}
}
The sendOrder function is set up like so:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(
order )
}
catch (e: Exception) {
Timber.e(e)
}
}
}
The GET request works perfectly.
Any help on this would be appreciated please.
In your Item Data Class you are using quantity as an Int but if you see the Postman JSON response it is a String.
So your class should be like:
data class Item(
#Json(name = "productUid")
val productUid: String?,
#Json(name = "quantity")
var quantity: String
)
Also as I see the key in your JSON response are written in two different ways.
For example your "Product ID" is written as "productUid" in one of the object and is written as "ProductUid" in another object.
So your complete Item Data Class should more look like this :
data class Item(
#Json(name = "productUid")
val productUid: String?,
#Json(name = "ProductUid")
val productUid: String?,
#Json(name = "quantity")
val quantity: String?,
#Json(name = "Quantity")
val quantity: String?
)
Add to app/build.gradle
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.13.0"
Refactor your data class
check the key in your item and replace with right one
if productUid or ProductUid
quantity or Quantity
#JsonClass(generateAdapter = true)
data class Item(
#Json(name = "productUid")
val productUid: String,
#Json(name = "quantity")
var quantity: String
)
#JsonClass(generateAdapter = true)
data class Order(
#Json(name = "items")
val items: List<Item>,
#Json(name = "customerName")
val customerName: String,
#Json(name = "customerPhoneNo")
val customerPhoneNo: String,
#Json(name = "customerAddress")
val customerAddress: String,
#Json(name = "note")
val note: String
)
and try it again
I have following class hierarchy Github Sample
interface OptionV2 {
val id: String
}
#JsonClass(generateAdapter = true)
class ImageSelectionOption(
override val id: String,
value: String,
#Json(name = "active_image")
val image: String?,
): OptionV2
#JsonClass(generateAdapter = true)
class QuestionResponse<T> (
override val id: String,
val answer: T?,
): OptionV2
And following test
val childOptions = listOf(ImageSelectionOption(value = "dummy", id = "dummy", image = "dummy"))
val childResponse = QuestionResponse<List<OptionV2>>(answer = childOptions, id = "child_qid")
val parentOptions = listOf(childResponse)
val parentResponse = QuestionResponse<Any>(answer = parentOptions, id = "parent_qid")
val moshi = Moshi.Builder().add(OptionV2MoshiAdapter.OptionAdapterFactory).build()
val type = Types.newParameterizedType(QuestionResponse::class.java, Any::class.java)
moshi.adapter<QuestionResponse<Any>>(type).toJson(parentResponse)
I am essentially attempting to deserialize QuestionResponse<List<QuestionResponse<List<Option>>>> type. This fails with following error
Failed to find the generated JsonAdapter constructor for 'class dev.abhishekbansal.moshilistinheritance.QuestionResponse'. Suspiciously, the type was not parameterized but the target class 'dev.abhishekbansal.moshilistinheritance.QuestionResponseJsonAdapter' is generic. Consider using Types#newParameterizedType() to define these missing type variables.
I wish to be able to write a custom adapter for this if needed. As I need to be able to deserialize this in the Retrofit scenario.
Here is more complete Github Sample
Update
Finally got it working by using this
// List<Option>
val listType = Types.newParameterizedType(List::class.java, OptionV2::class.java)
// QuestionResponse<List<Option>>
val qr1 = Types.newParameterizedType(QuestionResponse::class.java, listType)
// List<QuestionResponse<List<Option>>>
val listType2 = Types.newParameterizedType(List::class.java, qr1)
// QuestionResponse<List<QuestionResponse<List<Option>>>>
val finalType = Types.newParameterizedType(QuestionResponse::class.java, listType2)
println(moshi.adapter<QuestionResponse<Any>>(finalType).toJson(parentResponse))
I am still confused about how can I write a custom adapter for this which can be supplied to Moshi instance which is supplied to Retrofit. So that it can be serialized on the fly.
Here is the Custom Adapter that worked for me. I have a couple of doubts in this but it works.
class QuestionResponseAdapter<T>(val elementAdapter: JsonAdapter<T>) : JsonAdapter<T>() {
override fun fromJson(reader: JsonReader): T? {
return elementAdapter.fromJson(reader)
}
override fun toJson(writer: JsonWriter, value: T?) {
elementAdapter.toJson(writer, value)
}
object QuestionResponseAdapterFactory : Factory {
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
if (!annotations.isEmpty()) {
return null // Annotations? This factory doesn't apply.
}
if (type !== QuestionResponse::class.java) {
return null // Not a QuestionResponse This factory doesn't apply.
}
// Handle Type erasure at runtime, this class does not need adapter with single level of generic though
val parameterizedType = Types.newParameterizedType(type, Any::class.java)
val elementAdapter: JsonAdapter<Any> = moshi.adapter(parameterizedType)
return QuestionResponseAdapter(elementAdapter).nullSafe()
}
}
}
In my case I added a similar custom method.
protected inline fun <reified T> convert(value: SomeGenericClass<T>): String {
val parameterizedType = Types.newParameterizedType(SomeGenericClass::class.java, T::class.java)
val adapter = moshi.adapter<SomeGenericClass<T>>(parameterizedType)
return adapter.toJson(value)
}
For instance you want to convert an object of SomeGenericClass<*> to JSON.
#JsonClass(generateAdapter = true)
class SomeGenericClass<T>(
#Json(name = "pages")
val pages: Int = 0,
)
#JsonClass(generateAdapter = true)
data class Book(
#Json(name = "id")
val id: String,
)
val book = SomeGenericClass<Book>(
pages = 100,
author = "Aaa",
...)
Then you should create a Moshi object and call convert(book).
See also 1 and 2.
I have a parameterized base class
#JsonClass(generateAdapter = true)
data class BaseResponse<T>(
#Json(name = "message")
val message: String?,
#Json(name = "data")
val data: T? = null
)
I want to get parse a JSON string and get the message value
private inline fun <reified T> getMessage(): String? {
return try {
val jsonStr = "{\"message\":\"Email or password not provided\"}"
val types = Types.newParameterizedType(
BaseResponse::class.java,
T::class.java
)
val moshiAdapter = Moshi.Builder().build().adapter(types)
val baseResponse = moshiAdapter.fromJson(jsonStr)
baseResponse?.message
} catch (exception: Exception) {
null
}
}
Got compile error at the adapter function
How I call this function
val str = getMessage<Any>()
You're not specifying that you're parsing a BaseResponse, just replace your adapter creation by this
val moshiAdapter = Moshi.Builder().build().adapter<BaseResponse<T>>(types)
I have an object that comes down from the API with another json object (with no named attributes) as one of its attributes:
"stickerData": {}
I would like this to be parsed into this object:
#JsonClass(generateAdapter = true)
class StickerDto(
#Json (name = "totalAnimatedStickers") val total: Int,
#Json(name = "pages") val pages: Int,
#Json(name = "data") val stickers: List<Sticker>
)
#JsonClass(generateAdapter = true)
class Sticker(
#Json(name = "name") val name: String,
#Json(name = "id") val id: String,
#Json(name = "stickerData") val stickerData: String,
)
The architecture of this app uses a single Retrofit instance for every API call:
private fun createNewUserApiClient(authRefreshClient: AuthRefreshClient,
preferencesInteractor: PreferencesInteractor): UserApiClient {
val moshi = Moshi.Builder()
.add(SkipBadElementsListAdapter.Factory)
return Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(Interactors.apiEndpoint)
.build()
.create(UserApiClient::class.java)
}
Which, uses this adapter that you can see getting attached above:
internal class SkipBadElementsListAdapter(private val elementAdapter: JsonAdapter<Any?>) : JsonAdapter<List<Any?>>() {
object Factory : JsonAdapter.Factory {
override fun fromJson(reader: JsonReader): List<Any?>? {
val result = mutableListOf<Any?>()
reader.beginArray()
while (reader.hasNext()) {
try {
val peeked = reader.peekJson()
result.add(elementAdapter.fromJson(peeked))
} catch (e: JsonDataException) {
Timber.w(e, "Item skipped while parsing:")
}
reader.skipValue()
}
reader.endArray()
return result
}
}
However, this adapter does not allow for the parsing of a JSON object as a string. If I try, it throws a
Gson: Expected a string but was BEGIN_OBJECT
error. Is there any way to get this adapter to parse attributes like this as raw strings, rather than looking for an object ?
The stickerData should be Object in POJO class, like this...
#JsonClass(generateAdapter = true)
class Sticker(
#Json(name = "name") val name: String,
#Json(name = "id") val id: String,
#Json(name = "stickerData") val stickerData: StickerData,
)
Can anyone please tell me why this is not working
Model class :
#JsonClass(generateAdapter = true)
data class InstagramBusinessAccountResponse(
val data : List<Account>
) {
data class Account(
#Json(name = "id") val id : String,
#Json(name = "instagram_business_account") val instagramBusinessAccount : InstagramBusinessAccount
) {
data class InstagramBusinessAccount(
#Json(name = "id") val id: String,
#Json(name = "name") val name: String,
#Json(name = "profile_picture_url") val profilePictureUrl: String = ""
)
}
companion object {
fun fromJson(json: String) : InstagramBusinessAccountResponse {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(InstagramBusinessAccountResponse::class.java)
return jsonAdapter.fromJson(json)!!
}
}
}
When parsing the following json
{"data":[{"instagram_business_account":{"id":"id","username":"name","name":"Suyash Chavan","profile_picture_url":"image"},"id":"id"}]}
with
InstagramBusinessAccountResponse.fromJson(json.toString())
...
companion object {
fun fromJson(json: String) : InstagramBusinessAccountResponse {
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(InstagramBusinessAccountResponse::class.java)
return jsonAdapter.fromJson(json)!!
}
}
gives instagramBusinessAccount null but if I don't use Custom field names with #Json i.e. replacing instagramBusinessAccount with instagram_business_account and profilePictureUrl with profile_picture_url, it works fine.
I was missing .add(KotlinJsonAdapterFactory()) in Moshi Builder.
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
It works now.