Creating Data Models (POJOs) to simplify development

Data Models

Data models are simple & standalone classes that don’t hold any business logic. Their sole purpose is to represent data along with data structure. These can be used to simplify development and make your code alot more bug and error free. As everything is dynamic and mutable in AppInventor, things are quite easy to get messed up. By using data classes we can reduce errors by performing validations. Plus some other features such as JSON serialization/deserialization can be done very easily using libraries such as gson.

Prerequisites

In this guide, we will create a very simple project to help you get started with data classes in your projects. We will be using icon-ext Rush to create data class extension written in kotlin Kotlin. So if you haven’t installed rush-cli, make sure to do so by following Official Docs or by watching this Youtube Tutorial.

  • Rush
  • Basic programming knowledge
  • An IDE (intellij_idea Intellij recommended)

Creating Data Model Extension

Initial Setup

First create an extension project by using RushCli.

Open up the project with Intellij Idea.


Create Model Class

Next create a new file called Post.kt to define the data model.

package com.dreamers.postmodel

data class Post(
    val userId: Int,
    val id: Int,
    val title: String,
    val body: String
) {
    companion object {
        @JvmStatic
        val empty = Post(-1, -1, "", "")
    }

    val isEmpty: Boolean get() = this == empty
}

Here we have defined a few properties on Post model. Lets see a brief overview.

  • userId - Unique id of user who made the post.
  • id - Unique id of the post.
  • title - Post title.
  • body - Post body.

Next we have added a staic Empty Post to the Post class as a helper field. It represents a post which doesn’t hold any data and is empty. This can be used for validation purposes. We have also added another helper field called isEmpty which tells whether current post hold data or is empty.


Working on extension

Next lets add some functionality to the PostModel extension.

package com.dreamers.postmodel

import com.google.appinventor.components.annotations.SimpleFunction
import com.google.appinventor.components.annotations.SimpleProperty
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent
import com.google.appinventor.components.runtime.ComponentContainer

@Suppress("FunctionName")
class PostModel(container: ComponentContainer) : AndroidNonvisibleComponent(container.`$form`()) {

    private var _currentPost: Post? = null

    @SimpleFunction(
        description = "Create a new post."
    )
    fun CreatePost(userId: Int, id: Int, title: String, body: String): Any {
        return Post(userId, id, title, body)
    }

    @SimpleFunction(
        description = "Check whether given post has data or not. Returns true if the given object is empty, null or is not a type of post."
    )
    fun IsEmpty(post: Any): Boolean {
        return if (post is Post) post.isEmpty else true
    }

    @SimpleFunction(
        description = "Check whether given post has data or not. Returns true if the given object is not empty, not null and is a type of post."
    )
    fun IsNotEmpty(post: Any): Boolean {
        return !IsEmpty(post)
    }

    @SimpleFunction(
        description = "Set value of current post in order to access other properties. Return true if CurrentPost was set successfully."
    )
    fun SetCurrentPost(post: Any): Boolean {
        if (post is Post) {
            _currentPost = post
            return true
        }
        return false
    }

    @SimpleProperty(
        description = "Get current post object set using SetCurrentPost. Returns null if no object is set."
    )
    fun CurrentPost(): Any? = _currentPost
}

Here we have created a function to construct a new Post with given parameters. We have also added basic validations like isEmpty & isNotEmpty. We have also added a field called _currentPost that keeps track of the post that you set.

Next let’s add getters for Post properties.

@SimpleProperty
fun UserId(): Int = _currentPost?.userId ?: -1

@SimpleProperty
fun Id(): Int = _currentPost?.id ?: -1

@SimpleProperty
fun Title(): String? = _currentPost?.title

@SimpleProperty
fun Body(): String? = _currentPost?.body

These getter functions can be used to access properties of _currentPost.

Currently the extension has following blocks:


JSON Serialization/Deserialization

When working with API keys, you have to parse JSON data. Wouldn’t it be great if you could just pass the json data directly to the extension and it gives you back post. Well this can be done using Gson quite easily. Lets implement it.

First create this helper class that helps in parsing JsonArray to list of posts.

package com.dreamers.postmodel

import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

class ParameterizedTypeList<T>(private val wrapped: Class<T>) : ParameterizedType {

    override fun getActualTypeArguments(): Array<Type> = arrayOf(wrapped)

    override fun getRawType(): Type = List::class.java

    override fun getOwnerType(): Type? = null
}

Next add functions to the extension.

// Add these imports
import com.google.gson.Gson
import com.google.gson.JsonParseException


@Suppress("FunctionName")
class PostModel(container: ComponentContainer) : AndroidNonvisibleComponent(container.`$form`()) {

    // Declare a gson variable
    private val gson = Gson()

    // Other functions....

    @SimpleFunction(
        description = "Create a new post object from JSON string."
    )
    fun PostFromJson(json: String): Any? {
        return try {
            gson.fromJson(json, Post::class.java)
        } catch (e: JsonParseException) {
            OnJsonParseError(json, e.message ?: e.toString())
            null
        }
    }

    @SimpleFunction(
        description = "Create a list of posts from JSON Array string."
    )
    fun PostListFromJson(json: String): YailList? {
        return try {
            val type = ParameterizedTypeList(Post::class.java)
            val posts: List<Post> = gson.fromJson(json, type)
            YailList.makeList(posts)
        } catch (e: JsonParseException) {
            OnJsonParseError(json, e.message ?: e.toString())
            null
        }
    }

    @SimpleFunction(
        description = "Convert a single post or list of posts to valid JSON string."
    )
    fun toJson(data: Any): String {
        return gson.toJson(data)
    }

    @SimpleEvent(
        description = "Event raised when an error occurs while deserializing JSON."
    )
    fun OnJsonParseError(inputJson: String, message: String) {
        EventDispatcher.dispatchEvent(this, "OnJsonParseError", inputJson, message)
    }

}

Now we have added three extra blocks that can parse json to post, jsonArray to a list of posts and convert post and list of posts to json.

Screenshot 2022-05-05 005637


Upgrade Gson library

Gson is avaiable inside AppInventor library but I had issues with it. So I upgraded to latest version. You can download gson:2.9.0 from given link below and add it inside deps folder and mention it inside rush.yaml.

Gson: 2.9.0 - Mvn Repository

rush.yaml

# Other specs...
deps:
  - gson-2.9.0.jar

progaurd-rules.pro

# Other rules...
-dontwarn com.google.gson.**

Compile extension

You can compile extension by running rush build -r in the root directory. It will generate out folder containing built extension.


Extension in action

We can now import the extension in our project and see it in action. We are going to use a sample api called {JSON} Placeholder to fetch data and parse it.


Designer Section

Screenshot 2022-05-05 at 01-18-14 Kodular Creator

The UI design is quite simple and straight forward so I wont go into details. Checkout the aia file.


Blocks Section

As you can see in the parseJsonData function, instead of using dictionaries for parsing json, we passed data directly to the extension and got a list of posts. We can then use SetCurrentPost block to change current working post. And access it’s properties through the property getters.

This is a very simple example. You can accomplish complex data structures and simplify development by moving complex logic to the extension.


Project Files

15 Likes

Thank you very much for this. This will surely help a lot