Skip to content

Kotlin property delegates to manage Android Intent and Bundle extras

License

Notifications You must be signed in to change notification settings

Takhion/android-extras-delegates

Repository files navigation

Android Extras Delegates

Contains a collection of Kotlin property delegates to manage Android Intent and Bundle extras.

See this article for more information.

Usage

Create an object (anywhere, although by convention it's inside an Activity) and declare inside it some delegated extension properties over Intent or Bundle (all types are supported):

package com.example

class SomeActivity : Activity() {

    object IntentOptions {
        var Intent.someExtra by IntentExtra.String()
    }
}

IntentExtra and BundleExtra are two empty object which serve as containers for all delegates.

If not specified, the name of the extra will be the fully qualified name of the containing class, followed by the property name (in the example: "com.example.SomeActivity.IntentOptions::someExtra"). It is also possible to specify a custom prefix instead of the fully qualified name of the class, which is useful when creating nested delegates (see below).

If a default value is not specified, the returning type will be nullable.

To use a delegate, you need to bring the container in which it's defined in the current scope, like so:

fun test(intent: Intent) {
    with(SomeActivity.IntentOptions) {
        val someExtraValue = intent.someExtra
        intent.someExtra = "hello"
    }
}

Activity Companion

Optionally, you can use the provided ActivityCompanion (or SimpleActivityCompanion if you don't have any "intent options") to aid in creating intents for an Activity:

class SomeActivity : Activity() {

    companion object : ActivityCompanion<IntentOptions>(IntentOptions, SomeActivity::class)

    object IntentOptions {
        var Intent.someExtra by IntentExtra.String()
    }

    fun test() {
        val someExtraValue = intent.options { it.someExtra }
    }
}

fun test(context: Context) {
    SomeActivity.start(context) {
        it.someExtra = "hello"
    }
    // or
    val someActivityIntent = SomeActivity.intent(context) {
        it.someExtra = "hello"
    }
}

Custom types

Creating your own delegates is simple, as all building blocks are already provided.

For example to create a LocalDate (from Joda-Time), persisted as a String in an Intent:

import me.eugeniomarletti.extras.intent.IntentExtra
import me.eugeniomarletti.extras.intent.base.String
import org.joda.time.LocalDate

fun IntentExtra.LocalDate(name: String? = null, customPrefix: String? = null) =
    String(
        reader = { it?.let(LocalDate::parse) },
        writer = { it?.toString() },
        name = name,
        customPrefix = customPrefix)

Or in a Bundle:

import me.eugeniomarletti.extras.bundle.BundleExtra
import me.eugeniomarletti.extras.bundle.base.String
import org.joda.time.LocalDate

fun BundleExtra.LocalDate(name: String? = null, customPrefix: String? = null) =
    String(
        reader = { it?.let(LocalDate::parse) },
        writer = { it?.toString() },
        name = name,
        customPrefix = customPrefix)

The reader takes the persisted value in and outputs the custom type, while the writer does the opposite.

By defining the custom extensions on IntentExtra/BundleExtra, they will show up together with the basic types in the IDE autocomplete.

Nested types

There are cases when a type is composed of multiple simpler types, like the following example:

data class User(val id: Long, val email: String, val birthDay: LocalDate)

It's possible to compose multiple delegates to create a single "super delegate" that can read/write such a type:

import android.content.Intent
import me.eugeniomarletti.extras.DelegateProvider
import me.eugeniomarletti.extras.defaultDelegateName
import me.eugeniomarletti.extras.intent.IntentExtra
import me.eugeniomarletti.extras.intent.base.Long
import me.eugeniomarletti.extras.intent.base.String
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

fun IntentExtra.User(
    idName: String? = null,
    emailName: String? = null,
    birthdayName: String? = null,
    customPrefix: String? = null)
    =
    object : DelegateProvider<ReadWriteProperty<Intent, User?>> {

        override fun provideDelegate(thisRef: Any?, property: KProperty<*>) =

            object : ReadWriteProperty<Intent, User?> {

                private val namePrefix = property.defaultDelegateName(customPrefix)

                private var Intent.id by IntentExtra.Long(name = idName, customPrefix = namePrefix)
                private var Intent.email by IntentExtra.String(name = emailName, customPrefix = namePrefix)
                private var Intent.birthday by IntentExtra.LocalDate(name = birthdayName, customPrefix = namePrefix)

                override operator fun getValue(thisRef: Intent, property: KProperty<*>): User? {
                    val id = thisRef.id ?: return null
                    val email = thisRef.email ?: return null
                    val birthday = thisRef.birthday ?: return null
                    return User(id, email, birthday)
                }

                override operator fun setValue(thisRef: Intent, property: KProperty<*>, value: User?) {
                    thisRef.id = value?.id
                    thisRef.email = value?.email
                    thisRef.birthday = value?.birthDay
                }
            }
    }

An example usage could be:

package com.example

object IntentOptions {
    var Intent.currentUser by IntentExtra.User()
}

This will read/write 3 extras:

  • com.example.IntentOptions::currentUser::id with type Long
  • com.example.IntentOptions::currentUser::email with type String
  • com.example.IntentOptions::currentUser::birthday with type LocalDate (which is actually String)

Note that this is made possible by passing a custom prefix for the nested property names, which is going to be the default delegate name for the "super delegate" (in this case com.example.IntentOptions::currentUser).

The extra names can also be specified explicitly if necessary.

Download

Note that this is using Kotlin 1.1.2-3.

Add the following to the build.gradle:

repositories {
    jcenter()
}
dependencies {
    compile 'me.eugeniomarletti:android-extras-delegates:1.0.5'
}