How to send object from Flutter to Android? - android

I have class in Flutter:
class Foo {
String id;
int power;
Foo (this.id, this.power);
Map toJson() => {
'id': id,
'power': power
};
}
I've created a instance of this object and pass it to Android via MethodChannel as a String:
_myChannel.invokeMethod('custom_event', {
'foo': foo.toJson().toString(),
});
In Android I retrive that string and want to convert to Foo object on Android side so:
val fooString = call.method.argument<String>("foo")
//output: {id: my_id, power: 23}
val response = fooString?.let { Response(it) }
//output {"id":"my_id","power":"23"}
Helper class:
class Response(json: String) : JSONObject(json) {
val data = this.optJSONArray(null)
?.let { 0.until(it.length()).map { i -> it.optJSONObject(i) } } // returns an array of JSONObject
?.map { FooJson(it.toString()) } // transforms each JSONObject of the array into Foo
}
class FooJson(json: String) : JSONObject(json) {
val id: String = this.optString("id")
val power: String = this.optInt("power")
}
How I can convert response to my Kotlin's class?

Since Foo just contains a string and an integer, both of which are supported by the default message codec, it would make sense to simply pass those two values in a map. (In fact, you already have a method that creates that map, toJson, though a better name might be toMap.)
Don't then call toString on that map; instead use:
_myChannel.invokeMethod('custom_event', foo.toMap());
You are now invoking the method and passing the map. At the native end, you are going to look for the members using their map keys (i.e. id and power) using .argument<T> as you've tried, as follows:
val id = call.method.argument<String>("id");
You could, of course, then create a POJO that wraps an object around those two values, if really necessary.
For more complex messages, consider Pigeon. If you really want to use JSON as your method of serialization, look at the existing JSON message codec.

Related

Reference an object in a class by using a string?

I want to reference an object within this class I have below:
class HerbData {
object Dill {
const val herbName: String = "This is Dill!"
const val scientificName: String = "Anethum Graveolens"
val dullThumbnail: Int = R.drawable.dill_thumbnail_attr
}
object Peppermint {
val herbName: String = "This is Peppermint!"
}
}
Is there anyway that I can reference the object by using a string in Kotlin? Here is somewhat what I mean:
HerbData."Dill".herbname
I can't find anything on this topic for Kotlin.
Another way you could do this is with an enum class. The advantage over a map is that you have a data structure you can reference directly in code, so you could use HerbData.Dill as well as HerbData["Dill"]. And that will enable you to take advantage of compile-time checking and lint warnings, refactoring, exhaustive pattern matching, code completion etc, because the data is defined in your code
enum class HerbData(
val herbName: String,
val scientificName: String? = null,
val dullThumbnail: Int? = null
) {
Dill("This is Dill!", "Anethum Graveolens", R.drawable.dill_thumbnail_attr),
Peppermint("This is Peppermint!");
companion object {
operator fun get(name: String): HerbData? =
try { valueOf(name) } catch(e: IllegalArgumentException) { null }
}
}
fun main() {
// no guarantee these lookups exist, need to null-check them
HerbData["Peppermint"]?.herbName.run(::println)
// case-sensitive so this fails
HerbData["peppermint"]?.herbName.run(::println)
// this name is defined in the type system though! No checking required
HerbData.Peppermint.herbName.run(::println)
}
>> This is Peppermint!
null
This is Peppermint!
Enum classes have that valueOf(String) method that lets you look up a constant by name, but it throws an exception if nothing matches. I added it as a get operator function on the class, so you can use the typical getter access like a map (e.g. HerbData["Dill"]). As an alternative, you could do something a bit neater:
companion object {
// storing all the enum constants for lookups
private val values = values()
operator fun get(name: String): HerbData? =
values.find() { it.name.equals(name, ignoreCase = true) }
}
You could tweak the efficiency on this (I'm just storing the result of values() since that call creates a new array each time) but it's pretty simple - you're just storing all the enum entries and creating a lookup based on the name. That lets you be a little smarter if you need to, like making the lookup case-insensitive (which may or may not be a good thing, depending on why you're doing this)
The advantage here is that you're generating the lookup automatically - if you ever refactor the name of an enum constant, the string label will always match it (which you can get from the enum constant itself using its name property). Any "Dill" strings in your code will stay as "Dill" of course - that's the limitation of using hardcoded string lookups
The question really is, why do you want to do this? If it's pure data where no items need to be explicitly referenced in code, and it's all looked up at runtime, you should probably use a data class and a map, or something along those lines. If you do need to reference them as objects within the code at compile time (and trying to use HerbData."Dill".herbName implies you do) then an enum is a fairly easy way to let you do both
Declare a Data Class
data class HerbData (
val scientificName: String,
val dullThumbnail: Int
)
Initialize a muteable map and put data in it
val herbData = mutableMapOf<String, HerbData>()
herbData.put("Dill", HerbData("Anethum Graveolens", R.drawable.dill_thumbnail_attr))
herbData.put("Peppermint", HerbData("Mentha piperita", R.drawable.peppermint_thumbnail_attr))
You can now just
herbData["Dill"]?.scientificName
class HerbData {
interface Herb {
val herbName: String
val scientificName: String
}
object Dill : Herb {
override val herbName: String = "This is Dill!"
override val scientificName: String = "Anethum Graveolens"
}
object Peppermint: Herb {
override val herbName: String = "This is Peppermint!"
override val scientificName: String = "Mentha piperita"
}
companion object {
operator fun get(name: String): Herb? {
return HerbData::class
.nestedClasses
.find { it.simpleName == name }
?.objectInstance as? Herb
}
}
}
println(HerbData["Dill"]?.herbName) // Prints: This is Dill!
println(HerbData["Peppermint"]?.scientificName) // Prints: Mentha piperita
println(HerbData["Pepper"]?.herbName) // Prints: null

How to covert the $document.data details to useful format so that I could use it in listviews

I would like to convert $document.data details to useful format so that I could use it for further applications. This is data from firestore documents.
private val mFireStore = FirebaseFirestore.getInstance()
mFireStore.collection("Users").whereEqualTo("lastName","H").whereEqualTo("firstName", "Uc").get()
.addOnSuccessListener{ documents ->
for(document in documents){
Log.d("TAG","${document.id}=>${document.data}")
Toast.makeText(applicationContext, "${document.id} => ${document.data}",
Toast.LENGTH_LONG).show()
}
}
.addOnFailureListener{exception ->
Log.w("TAG","Error getting documents:",exception)
Toast.makeText(applicationContext, "Failed",
Toast.LENGTH_LONG).show()
}
This is my code. Now when I run the code get this in the logcat
OL0rD4UfgHSh2K8UoTMnX6Xea6P2=>{lastName=H, image=, firstName=Uc, B=L, gender=, organization=, profileCompleted=0, mobile=0, blood group=o+, id=OL0rD4UfgHSh2K8UoTMnX6Xea6P2, email=jojoy09#gmail.com}
Now I want to convert this result to a useful format so that I could use it later. I wpuld like to convert the data so that I could load it in listview.
In the following for-loop:
for(document in documents) { ... }
The "document" object is of type DocumentSnapshot. When you call getData() on such an object, the type of object that is returned is a Map<String, Object>.
In Kotlin, this object is represented by Map<String, Any>. In order to get the data, you can simply iterate over the Map and get the data accordingly, using the following lines of code:
val map = document.data
for ((key, value) in map) {
Log.d("TAG", "$key = $value")
}
Or even simpler, using:
map.forEach { (key, value) -> Log.d("TAG", "$key = $value") }
However, if you only need, the value of a particular property, for example, the value of the email address, you can simply get it by using DocumentSnapshot#getString(String field) method:
val email = document.getString("email")
Log.d("TAG", email)
The result in the logcat will be:
jojoy09#gmail.com
.................
As I see in your screenshot, almost all of the properties are of type String. However, you can find different flavors for each type of field, like getLong(String field), getDouble(String field), getDate(String field), getTimestamp(String field), and so on.
Furthermore, if you need to get the entire document, and you want to convert it into an object of a specific class, as also #mehulbisht mentioned in his answer, you should use DocumentSnapshot#toObject(Class valueType). So assuming that you have a data class that looks like this:
data class User(
var email: String? = null,
var firstName: String? = null,
var lastName: String? = null,
//Other necessary fields
)
To convert the "document" object into an object of the "User" class, please use the following line of code:
val user = document.toObject(User::class.java)
Log.d("TAG", user.email)
The result in the logcat will be the same as above.
If you want to display a list of "User" objects in a ListView, then please check my answer from the following post:
What miss, with connect Firestore and ListView for random results in sample?
It's really simple to convert the Java code into Kotlin.
The Model that you used to set this data will be used here. You can convert the documents to your Model class using the .toObjects() method on it. Just use:
val myObjs = documents.toObjects(Model::class.java)
EDIT
For displaying this as a Log in Logcat use:
Log.d("myObjs ","""
$myObjs
""".trimIndent())
Do tell if this doesn't work for you :)

Multi type object in kotlin

from an API call i get as response a body with this structure
open class BaseResponseEntity {
#SerializedName("result")
val result: ResultEnum = ResultEnum.NONE
#SerializedName("errorCode")
val errorCode: String = ""
#SerializedName("errorMessage")
val errorMessage: String = ""
#SerializedName("successMessage")
val successMessage: String = ""
#SerializedName("value")
val code: LastPaymentCodeModel?
}
where the field "value" can be three types: null, String or LastPaymentCodeModel. How can i get this?
I managed to put a ? so that both null and LastPaymentCodeModel are handled, but i don't know how to handle the String type too.
I think the best approach would probably be to use type Any? for code.
Then you should write a custom GSon serializer/deserilizer (JsonDeserializer<BaseResponseEntity>) for the BaseResponseEntity object.
In this Json deserializer, you would need to check the type of value (e.g is it a string or a data structure) and decode it to the correct object type.
Alternative, to avoid the use of Any?, you could leave the model exactly as you have it. You will still need to write a custom JsonDeserializer, however if value is a string then it would still create a LastPaymentCodeModel, using the string value as one of it's properties.

Android - Populate values into already created POJO model object using retrofit

I have a POJO model object "apiResponse" that has values from a previous API call.
{
"status":200,
"userModel":{
...SOME VARIABLES...
},
"otherContentRelatedToUserModel":{
..SOME RELATED CONTENT..
}
}
This apiResponse has an "UserModel" as an inner object.
What i want to do is pass this "apiResponse" object to another api call whose response is "UserModel", and have it update only the UserModel object in the APIResponse POJO object.
The objective is to keep a single source of related content, which could change based on the interaction in the application, but might not update the rest of the related content.
Or is it possible to atleast update an already created pojo model as a whole, updating the variable values in the model.?
Reason for this ::
The API's content does not change for a set amount of time in the server, mainly to avoid over traffic to the server. So some amount of logic has to be implemented on the application side. Currently using a DB is not really a viable option.
Basically update only a portion of the already created POJO class object with another api call.
Is this possible in android(kotlin) using retrofit? Or is there any other way this could be achievable?
I think it is not possible to populate the additional fields in the existing UserModel object by using Retrofit, but you can do some magic with GSON:
data class UserModel(
val userId: Int? = null,
val userName: String? = null)
class OtherContentRelatedToUserModel
data class ApiResponsePojo(
val status: Int? = null,
val userModel: UserModel? = null,
val otherContentRelatedToUserModel: OtherContentRelatedToUserModel? = null)
class UserModelInstanceCreator(var userModelToUpdate: UserModel? = null)
: InstanceCreator<UserModel> {
override fun createInstance(type: Type?): UserModel {
return userModelToUpdate ?: UserModel()
}
}
val apiResponseJson =
"""
{
"status":200,
"userModel":{
"userId": 1
},
"otherContentRelatedToUserModel":{
}
}
"""
val userModelResponseJson =
"""
{
"userName": "john wick"
}
"""
val userModelInstanceCreator = UserModelInstanceCreator()
val gson = GsonBuilder()
.registerTypeAdapter(UserModel::class.java, userModelInstanceCreator)
.create()
val apiResponse: ApiResponsePojo = gson.fromJson(apiResponseJson, ApiResponsePojo::class.java)
userModelInstanceCreator.userModelToUpdate = apiResponse.userModel
gson.fromJson(userModelResponseJson, UserModel::class.java)
...
// apiResponse.toString() result
// ApiResponsePojo(status=200, userModel=UserModel(userId=1, userName=john wick)...

Why don't I get correct result when I use original parseList function in Kotlin?

I'm learning the sample code about Anko at Kotlin for Android Developers (the book) https://github.com/antoniolg/Kotlin-for-Android-Developers
The Method 1 is from sample code and override parseList ,but it's hard to understand.
So I try to use the Method 2 instead of the Method 1, the Method 2 use original parseList function, but I get blank record when I use the Method 2, what error do I made in the Method 2
class DayForecast(var map: MutableMap<String, Any?>) {
var _id: Long by map
var date: Long by map
var description: String by map
var high: Int by map
var low: Int by map
var iconUrl: String by map
var cityId: Long by map
constructor(date: Long, description: String, high: Int, low: Int,
iconUrl: String, cityId: Long) : this(HashMap()) {
this.date = date
this.description = description
this.high = high
this.low = low
this.iconUrl = iconUrl
this.cityId = cityId
}
}
Method 1
override fun requestForecastByZipCode(zipCode: Long, date: Long) =
forecastDbHelper.use {
val dailyRequest = "${DayForecastTable.CITY_ID} = ? AND ${DayForecastTable.DATE} >= ?"
val dailyForecast = select(DayForecastTable.NAME)
.whereSimple(dailyRequest, zipCode.toString(), date.toString())
.parseList { DayForecast(HashMap(it)) }
/* common code block */
}
fun <T : Any> SelectQueryBuilder.parseList(parser: (Map<String, Any?>) -> T):
List<T> = parseList(object : MapRowParser<T> {
override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
})
Method 2
override fun requestForecastByZipCode(zipCode: Long, date: Long) =
forecastDbHelper.use {
val dailyRequest = "${DayForecastTable.CITY_ID} = ? AND ${DayForecastTable.DATE} >= ?"
val dailyForecast = select(DayForecastTable.NAME)
.whereSimple(dailyRequest, zipCode.toString(), date.toString())
.exec { parseList(classParser<DayForecast>()) }
/* common code block */
}
I really do think you should stick to using the 'method 1' approach, it's a lot easier once you realise what Kotlin is letting you do. As I don't know how much you know about Kotlin, I'll try to cover this completely.
The existing class SelectQueryBuilder has (I presume) a function called parseList, that existing function takes a MapRowParser<T>. The MapRowParser<T> has a function parseRow that takes a Map<String, Any?> and returns a T.
In the old Java way of working, you would derive from MapRowParser<T> and would override parseRow so that it does the conversion you want; converting the Map<String, Any?> into a DayForecast (the generic T would now have a type). An instance of this derived class is passed into the existing parseList function. Your derived class would look something like
class MapToDayForecastRowParser extends MapRowParser<DayForecast> {
#Override public DayForecast parseRow(Map<String, Object> map) {
// Note that Java's "Object" is more or less Kotlin's "Any?"
return new DayForecast(map); // Might need to convert the map type btw
}
}
The extension method is making it really easy to wrap/hide/abstract that creation of the derived class. The extension method take a lambda, that is, you have to parse into the new parseList method a block of code that takes a Map<String, Any?> and returns T (this is what DayForecast(HashMap(it)) is doing, the it is an automatically named variable that is the Map. The extension method then calls the existing parseList method, parsing in an anonymous class that it creates itself. That means that ever use of this extension method creates a new anonymous class, but the Kotlin compiler handles this very well.
One part that did confuse me at first is the way Kotlin handles the anonymous class.
// Java
new MapRowParser<T>() {
#Override public T parseRow(Map<String, Object>) {
/* Map to T logic */
}
}
// Kotlin
object : MapRowParser<T> {
override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
}
Kotlin also makes it very easy to handle the 'lambda'. It is parsed into the extension method as parser and then set as the implementation of our anonymous class parseRow function. You can also reuse them if you so wished, your so if you need to do the same sort of parsing in lots of places, you can use a named function instead.
The great advantage to this new Kotlin way is that it's very easy focus on what you want to do. With that extension method in place, it's very quick to re-use it so that in another query you can do parseList{ it.getOrDefault("name", "unkown_user") }. You can now easily work on just thinking "If each row is a map, how do I convert that down to a value I want?".

Categories

Resources