Is there a convenient way to create Parcelable data classes in Android with Kotlin?

Is there a convenient way to create Parcelable data classes in Android with Kotlin?

Jana Lailoken Author: Jana Lailoken Date: 2022-08-15
Is there a convenient way to create Parcelable data classes in Android with Kotlin?

All you need to know about Is there a convenient way to create Parcelable data classes in Android with Kotlin? , in addintion to android - Is there a way to have 1 Firebase database for 2 apps with different package names? , android - createOrUpdate() always creates a new entry in the database with ORMLite , Is there a way to create an emulator with XXXHDPI density on Android Studio? , android - Is there any way to handle unchecked cast warning without using Supress in Kotlin?

  1. Is there a convenient way to create Parcelable data classes in Android with Kotlin?
  2. Question:

    I'm currently using the excellent AutoParcel in my Java project, which facilitates the creation of Parcelable classes.

    Now, Kotlin, which I consider for my next project, has this concept of data classes, that automatically generate the equals, hashCode and toString methods.

    Is there a convenient way to make a Kotlin data class Parcelable in a convenient way (without implementing the methods manually)?


    Solution 1:

    Kotlin 1.1.4 is out

    Android Extensions plugin now includes an automatic Parcelable implementation generator. Declare the serialized properties in a primary constructor and add a @Parcelize annotation, and writeToParcel()/createFromParcel() methods will be created automatically:

    @Parcelize
    class User(val firstName: String, val lastName: String) : Parcelable
    

    So you need to enable them adding this to you module's build.gradle:

    apply plugin: 'org.jetbrains.kotlin.android.extensions'
    
    android {
        androidExtensions {
            experimental = true
        }
    }
    

    Solution 2:

    You can try this plugin:

    android-parcelable-intellij-plugin-kotlin

    It help you generate Android Parcelable boilerplate code for kotlin's data class. And it finally look like this:

    data class Model(var test1: Int, var test2: Int): Parcelable {
    
        constructor(source: Parcel): this(source.readInt(), source.readInt())
    
        override fun describeContents(): Int {
            return 0
        }
    
        override fun writeToParcel(dest: Parcel?, flags: Int) {
            dest?.writeInt(this.test1)
            dest?.writeInt(this.test2)
        }
    
        companion object {
            @JvmField final val CREATOR: Parcelable.Creator<Model> = object : Parcelable.Creator<Model> {
                override fun createFromParcel(source: Parcel): Model{
                    return Model(source)
                }
    
                override fun newArray(size: Int): Array<Model?> {
                    return arrayOfNulls(size)
                }
            }
        }
    }
    

    Solution 3:

    Just click on the data keyword of your kotlin data class, then press alt+Enter, select the first option saying "Add Parceable Implementation"

  3. android - Is there a way to have 1 Firebase database for 2 apps with different package names?
  4. Question:

    I have 2 applications(different package names) catering to 2 different user groups. One puts data into database and the other retrieves it. Therefore I want only 1 firebase database for both the apps. Firebase allows only 1 database for 1 package name as far as I know. Is there a way to solve this problem and have 1 Firebase database for 2 apps?


    Solution 1:

    Yes this is possible using the Firebase console. You only get one database per project but you can have many apps per project just by adding them in the console. They don't need to have the same package name or signing key.

    If you have some reason why you can't add them both to the same project, you will have to configure at least one of the apps manually in client side code. So rather than using the google-services.json file for configuration, you can call FirebaseApp.initializeApp() manually and fill in the information so both apps are talking to the same Firebase project.

  5. android - createOrUpdate() always creates a new entry in the database with ORMLite
  6. Question:

    In my Android project, ORMLite is functioning as a cache. I'm downloading data from a web server and placing it in the database. I'm calling createOrUpdate on my objects, but duplicates are appearing in the database. The database entries are identical except for the primary key (which is simply an auto incremented integer). I think that since my second object doesn't yet have a primary key, ORMLite considers the two as being different, even though every other field is identical.

    Does anyone know if this is true?


    Solution 1:

    You should not be calling createOrUpdate unless your object already has an id field set. The way ORMLite determines whether or not it exists in the database is to do a query-by-id on it. The code does:

    ID id = extractId(data);
    // assume we need to create it if there is no id  <<<<<<<<<<<<<<<<<<<<<<<
    if (id == null || !idExists(id)) {
        int numRows = create(data);
        return new CreateOrUpdateStatus(true, false, numRows);
    } else {
        int numRows = update(data);
        return new CreateOrUpdateStatus(false, true, numRows);
    }
    

    I'll expand the javadocs to explain this better. They are very weak there. Sorry. I've updated them to be:

    This is a convenience method for creating an item in the database if it does not exist. The id is extracted from the data argument and a query-by-id is made on the database. If a row in the database with the same id exists then all of the columns in the database will be updated from the fields in the data parameter. If the id is null (or 0 or some other default value) or doesn't exist in the database then the object will be created in the database. This also means that your data item must have an id field defined.

    Solution 2:

    Solution 3:

    set your primary key for it to work normally.

    @DatabaseField(columnName = "id",uniqueIndex = true) private int id;

    Worked for me. Exceptions are thrown at background but it works :D

  7. Is there a way to create an emulator with XXXHDPI density on Android Studio?
  8. Question:

    I want to test my work on XXXHDPI device. Instead of buying one, is it possible to use Android Studio Emulator to make one? Currently I see the all density is there from ldpi to xxhdpi, but not xxxhdpi.

    p/s: Actualy I have a an S7 device which is XXXHDPI, but wanted another to confirm some behavior is specific to XXXHDPI as per question in Where did my 10dp height button goes?.


    Solution 1:

    You can create an emulator of Nexus 6 or Nexus 6P. They have a density of 3.5, which is XXXHDPI.

    Reference

  9. android - Is there any way to handle unchecked cast warning without using Supress in Kotlin?
  10. Question:

    I am using ViewModelFactory in my android app to pass some data to my ViewModel from Fragment. I'm getting unchecked cast warning. If I'm using Supress the warning goes away. I was wondering is there any way to handle this without using Supress("UNCHECKED_CAST")

    The code I'm using to create the ViewModelFactory

    val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
            //factory to pass necessary data to ViewModel
            @NonNull
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                return activity?.application?.let {
                    BookReaderViewModel(
                        it,
                        "local",//todo:remote or local book. value will come from arguments
                        1//todo: bookId will come from arguments
                    )
                } as T
            }
        }
    

    as T is getting the warning.


    Solution 1:

    No, an unchecked cast means the compiler cannot guarantee a cast will fail or succeed.

    In most cases there is nothing you can add to change that because the compiler simply does not have enough information. This usually happens around generis as T can represent many different types.
    You have to add the correct type checks before casting so you are sure the cast succeeds.

    You can suppress the warning. By doing that you, as a developer, promise the compiler that you are sure that the cast will always succeed.


    The code as it is can't make that promise. If the create function gets called with anything else than a BookReaderViewModel it will crash because the cast doest not succeed.

    Here's example where it's safe to suppress the warning because it checks before it casts:

        @NonNull
        @Supress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            val application = activity?.application ?: return null
            if (modelClass == BookReaderViewModel::class.java) {
                return BookReaderViewModel(
                    application,
                    "local",
                    1) as T
            }
            // return null or throw exception here
            throw IllegalArguentException("Factory cannot make ViewModel of type ${modelClass.simpleName}")
        }