Simplifying API consumption to Android Apps from Basic to Advanced — part2: JSON and XML
Now we have ways to send information from client to server, I wouldn’t expect receiving a simple “hello world” would satisfy the necessities of your project.
JSON
If you are facing something like
{
“latitude”: 53.5,
“longitude”: 14.219999,
“generationtime_ms”: 1.5190839767456055,
“utc_offset_seconds”: 0,
“timezone”: “GMT”,
“timezone_abbreviation”: “GMT”,
“elevation”: 35
}
You have to parse from .json to your model class. First of all, do not write your model manually. You can find on the internet many json2<language>. So enjoy it.
For this sample, I used json2kt.com to generate JSON files. But there are many options to do the same.
Model Class
data class Location( @SerializedName("latitude")
var latitude: Double? = null,
@SerializedName("longitude")
var longitude: Double? = null,
@SerializedName("generationtime_ms")
var generationtimeMs: Double? = null,
@SerializedName("utc_offset_seconds")
var utcOffsetSeconds: Int? = null,
@SerializedName("timezone")
var timezone: String? = null,
@SerializedName("timezone_abbreviation")
var timezoneAbbreviation: String? = null,
@SerializedName("elevation")
var elevation: Int? = null)
Adding library
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
API
Our API Interface returns our class instead of a ResponseBody
interface Api {
@GET("/v1/forecast")
fun getLocation(@Query("latitude") latitude : Double,
@Query("longitude") longitude : Double)
: Call<Location>
}
Adding converter
Retrofit.Builder()
.baseUrl("https://api.open-meteo.com/")
.addConverterFactory(GsonConverterFactory.create())
.build().create(Api::class.java)
.getLocation(53.0, 14.0)
.enqueue(object : Callback<Location> {
override fun onResponse(call: Call<Location>,
response: Response<Location>) {
val location : Location? = response.body()
println(location)
} override fun onFailure(call: Call<Location>, t: Throwable) {
t.printStackTrace()
} })
This will be enough to run your project in debug mode, but some classes stop working after being encrypted. If not be clear to you why we are talking about encryption, basically you won’t wish to expose your code when you send your code to GooglePlay. Ok, you send them only a compiled project, but there are some reverse engineering tools.
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-rules.pro'),
'proguard-rules.pro'
}
}
}
When you enable the minify flag for a specific build type, that will produce an encrypted code, but sometimes it breaks our project. So, we can skip some stuff from our encryption based on our proguard-rules.pro. Some projects required some entries in that file.
XML
If you are facing something like
<channel id="1">
<title>W3Schools Home Page</title>
<link>https://www.w3schools.com</link>
<description>Free web building tutorials</description>
<item>
<title>RSS Tutorial</title>
<link>https://www.w3schools.com/xml/xml_rss.asp</link>
<description>New RSS tutorial on W3Schools</description>
</item>
<item>
<title>XML Tutorial</title>
<link>https://www.w3schools.com/xml</link>
<description>New XML tutorial on W3Schools</description>
</item>
</channel>
You need an XML Parser. To parse a JSON file, there is one main library (Gson), as you saw that json2Kotlin brought a SerializedName annotation, and that belongs to the Gson library (alternatively, I’d recommend you look for Moshi), but here, when we handle an XML, unfortunately, there is not one main library, so I will use here the Tickaroo library but fill free to look another one. I just explained why maybe you will find some difficult to find a good “xml2Kt” online converter, then I recommend you write each line of your model class.
Adding kapt support
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
Adding libraries
implementation 'com.tickaroo.tikxml:retrofit-converter:0.8.13'
implementation 'com.tickaroo.tikxml:annotation:0.8.13'
kapt 'com.tickaroo.tikxml:processor:0.8.13'
Model Class
@Xml(name = "channel")
class Channel(
@Attribute(name = "id")
val id : String? = null,
@PropertyElement(name = "title")
val title: String? = null,
@PropertyElement(name = "link")
val link: String? = null,
@PropertyElement(name = "description")
val description: String? = null,
@Element
val items : List<Item> = emptyList()
)@Xml(name = "item")
data class Item(
@PropertyElement(name = "title")
val title: String? = null,
@PropertyElement(name = "link")
val link: String? = null,
@PropertyElement(name = "description")
val description: String? = null
)
API
interface Api { @GET("v3/5df31685-22de-4f94-9cb2-58b36a8375e9")
fun getChannel()
: Call<Channel>
}
Adding Converter
Retrofit.Builder()
.baseUrl("https://run.mocky.io/")
.addConverterFactory(TikXmlConverterFactory.create())
.build().create(Api::class.java)
.getChannel()
.enqueue(object : Callback<Channel> {
override fun onResponse(call: Call<Channel>,
response: Response<Channel>) {
val channel : Channel? = response.body()
println(channel)
} override fun onFailure(call: Call<Channel>, t: Throwable) {
t.printStackTrace()
} })