\ In this text, I want to discuss such specific issues with AMA requests on Android as:
\
Let's suppose we have to get a list of users where our presentation layer model will look like this:
\
data class UserItemModel( val id: Int, val fullName: String, val phone: List\ UMA will send us a response:
\
{ "field_name": "id", "schema": [ "users_table", "id" ], "type": "integer" }, { "field_name": "fullName", "schema": [ "users_table", "fullName" ], "type": "string" }, { "field_name": "array_agg", "schema": [ "users_table", "array_agg" ], "type": "ARRAY" }, { "field_name": "array_agg", "schema": [ "users_table", "array_agg" ], "type": "ARRAY" }, { "field_name": "array_agg", "schema": [ "emails", "array_agg" ], "type": "ARRAY" }\ Now let's remember what our basic decoder function looks like:
\
inline fun\ As you might have noticed, we may receive fields with identical field names "field_name" : "array_agg" and even totally identical meta as we have for phone and telegram:
\
{ "field_name": "array_agg", "schema": [ "users_table", "array_agg" ], "type": "ARRAY" },\ However, in our decoder, we parse data by the field name, supposing that it is unique. So we need to handle arrays by the schema name in the email case and by data value check in the phone and telegram case.
\ First of all, let's add some constants:
\
const val ARRAY_AGG = "array_agg" const val USERS_TABLE = "users_table" const val EMAILS = "emails" const val TELEGRAM = "telegram" const val PHONE = "phone"\ Where we will use the EMAILS, TELEGRAM and PHONE values in our response class like this:
\
data class UserResponse( var id: Int?, var fullName: String, var phone: List\ Now, instead of just:
\
val field: Field = T::class.java.getDeclaredField(meta.field_name)\ We will add a when statement and add conditions:
\
val field: Field = if (meta.field_name == ARRAY_AGG) { when (meta.schema[0]) { EMAILS -> T::class.java.getDeclaredField(EMAILS) USERS_TABLE -> { val formattedValue = value.toString().replace("[\\[\\],\\s+]".toRegex(), "") if (formattedValue.isDigitsOnly()) T::class.java.getDeclaredField(PHONE) else T::class.java.getDeclaredField(TELEGRAM) } else -> T::class.java.getDeclaredField(meta.field_name) } } else { T::class.java.getDeclaredField(meta.field_name) }\ Here, we check if the meta's field name is array_agg. If so, we check the first schema’s value. If it's an email, find the same name in the response class.
\ For phone and telegram we have identical meta values. So the question is how to distinguish between them.
\ So our next step will be checking if the value equals users_table, then if the data value under this meta consists only of digits. If so, we will search for the PHONE field differently than for TELEGRAM.
Dynamically extendable data in responseImagine we have a list of websites with the number of users that were online on certain days (the range we specify in the request, let's say 8.10.2024 - 11.10.2024, so it will return 3 days)
And we get the following response:
\
{ "result": { "data": [ [ "facebook", 456, 280, 653, 942 ], [ "pinterest", 556, 299, 342, 673 ], ], "meta": [ { "field_name": "website_name", "schema": [ "online_data_tbl", "project_name" ], "type": "straing" }, { "field_name": "day_1", "schema": [ "online_data_tbl", "day_1" ], "type": "int" }, { "field_name": "day_2", "schema": [ "online_data_tbl", "day_2" ], "type": "int" }, { "field_name": "day_3", "schema": [ "online_data_tbl", "day_3" ], "type": "int" }, { "field_name": "total_users", "schema": [ "online_data_tbl", "total_users" ], "type": "int" }, ] } }\ You can see that we have a separate field for each day containing an int value, not a list of ints. To handle this type of response, we must first of all create a response class:
\
class UsersOnlineResponse { var website_name: String? var total_users: Int? var daysList: MutableList\ We will wait for daysList as for the usual list, but we added the addDayElement function, which extends the list. This function will be called in our decoder. But first, let's add some constants:
\
const val DAY_ELEMENT = "day_" const val ADD_DAY_ELEMENT_FUNC_NAME = "addDayElement"\ ADD_DAY_ELEMENT_FUNC_NAME in this field we have to store the exact name of our addDayElement function.
In the .toDecoded() function we will add a check for DAY_ELEMENT
\
inline fun\ This is what our final decoder function looks like.
Types mismatch issuesIn UMA responses, we often get improper types. For example, meta says we receive an integer, but we may get double inside, and if in our response class we expect an int but get a float, the value will not be parsed. For enum, we'll receive string values and as for strings, the meta may say that it's a string and a text type. To handle this situation, we’ll add a type check to the decoder. Let's replace our val fieldValue = i[index] with this:
\
// loop and constructor code meta?.forEachIndexed { index, meta -> val value = i[index] val fieldValue = when (meta.type) { "boolean" -> { value as Boolean } "integer" -> { if (value is Double) value.toInt() else value } "string", "text" -> { value as String? } "enum" -> { value as String? } else -> { value } } // rest of the code related to field\ In this article, we looked at the main issues you might encounter when working with UMA. In future articles, we'll discuss how to work with pagination, sorting, and how to build your own request using table joins.
All Rights Reserved. Copyright , Central Coast Communications, Inc.