Some time ago, I completed the development of a new software mobile toolbox. Recently, I decided to deposit some of the things I learned during development into my own open source libraries. Some of the latest updates take advantage of some of the syntax features of Kotlin and Jetpack, so to summarize. Jetpack and Kotlin have been around for a long time, but a lot of application development is still stuck in the MVP and Java phase, and even with Kotlin, a lot of people are just using Kotlin like Java. In fact, the syntactic features of Kotlin and Jetpack can greatly improve the efficiency of our daily development. Below, I illustrate the new changes made by Jetpack and the use of Kotlin’s features with some new Feature updates from my own open source library.

1, the Android – VMLib

The library’s address is: github.com/Shouheng88/…

1.1 New Data interaction design: Hide LiveData

In fact, I mentioned this library in an earlier article, “2020, This is how I Use MVVM for Projects”. However, I made some changes in the new version. The first is to upgrade some of the tripartite libraries that the application relies on to the latest version. Second, in BaseActivity and BaseFragment, I’ve added a few more methods,

// New observation method, UI layer by specifying class, flag and single to directly listen to data
protected fun <T> observe(dataType: Class<T>,
                          flag: Int? = null,
                          single: Boolean = false,
                          success: (res: Resources<T- > >)Unit = {},
                          fail: (res: Resources<T- > >)Unit = {},
                          loading: (res: Resources<T- > >)Unit = {}) {
    vm.getObservable(dataType, flag, single).observe(this, Observer { res ->
        when(res? .status) { Status.SUCCESS -> success(res) Status.LOADING -> loading(res) Status.FAILED -> fail(res) } }) }Copy the code

In fact, I mentioned this problem in previous articles. We use custom enumerations to divide the UI layer and ViewModel interactions into successful, failed, and loading states, and then make judgments at the UI layer based on the states to process each state separately. So, in that case, why don’t we write each state as a method to call back separately? Of course, here we use Kotlin’s functional programming to achieve this goal better. Furthermore, we specify a default empty implementation for each function so that the user only needs to implement the specified state according to his or her needs.

In addition, I added the following methods to the ViewModel,

// The top-level viewModel adds a new method that wraps three states of interaction
fun <T> setSuccess(dataType: Class<T>, flag: Int? = null, single: Boolean = false.data: T) {
    getObservable(dataType, flag, single).value = Resources.success(data)}fun <T> setLoading(dataType: Class<T>, flag: Int? = null, single: Boolean = false) {
    getObservable(dataType, flag, single).value = Resources.loading()
}

fun <T> setFailed(dataType: Class<T>, flag: Int? = null, single: Boolean = false, code: String? , message:String?). {
    getObservable(dataType, flag, single).value = Resources.failed(code, message)
}
Copy the code

Basically, you wrap up the logic that used to fetch LiveData and divide the three states into three methods. So, now the UI’s interaction with the ViewModel looks like this,

That is, the UI layer makes the request by calling the methods of the ViewModel (flow 1 in the figure), When the ViewModel completes the request, the state is passed to the UI layer via LiveData by calling setSuccess(), setLoading(), and setFailed() methods. The UI layer listens through the observe() method. Here we use Class, Flag, and Boolean to jointly determine a LiveData. Specifically, the Class itself can determine the LiveData, where the flag is used to distinguish between the same Class. For example, an article has a title and content, both of which are Stirng types. How to distinguish them? In this case, you need to use flags to distinguish. The Boolean here defines “one-time” LiveData, which means that once notification is made, no second notification will be made.

Use a piece of code to illustrate the interaction logic,

Here the UI layer initiates the request through the requestFirstPage() of the ViewModel, In ViewModel, setSuccess(), setLoading(), and setFailed() methods are used to pass state through LiveData to the UI layer. The UI listens with the observe() method, but only the “succeeded” and “failed” states are handled here. This new way of development requires much less code than before, and even the UI layer only needs one line of code to complete the logic of registering listeners.

What we’re doing here is hiding the concept of LiveData. However, the development of finding LiveData by Class is relatively new, and there may be a better way to hide LiveData to better implement registration and notification logic. So it’s safe to say that Jetpack’s LiveData could revolutionize the way Android is developed. However, I think Android’s MVVM implementation is still lacking: Google officially provides Jetpack and Databinding as a set of things to implement the MVVM architecture, but the Android implementation is much more cumbersome than Vue and the like. First, the Databinding compilation speed is a problem, and second, when Android does Databinding, variables must be declared before being used, which doesn’t make the code any cleaner. In addition, while Android provides style to define the properties of controls, style does not offer the same richness of functionality as a selector on the front end, and thus does not offer the same separation of style, logic, and layout as the front end. This leads to a very verbose layout written in XML, and further data binding in XML makes the XML even more verbose.

One more thing to note here is Kotlin’s functional programming. As you can see above, our Observe () method does state callbacks by defining three functions, whereas in Java you can only use interfaces for callbacks. Java’s use of interfaces for functional programming is not as thorough as Kotiln’s, and even the use of interface callbacks in Kotlin is not as elegant. So, in the new version, I also migrated some classes onto Kotlin to take full advantage of Kotlin’s features.

2, Android – Utils

The library’s address is: github.com/Shouheng88/…

2.1 Added a new wave of methods

First, there are a number of new methods added to the library, such as generating Drawable in code, that help us achieve theme compatibility in code, because using Drawable in custom Drawable. XML files? Attr compatibility issues in lower versions, we can implement part of the logic in code,

// https://github.com/Shouheng88/Android-utils/blob/master/utils/src/main/java/me/shouheng/utils/ui/ImageUtils.java
public static Drawable getDrawable(@ColorInt int color,
                                   float topLeftRadius,
                                   float topRightRadius,
                                   float bottomLeftRadius,
                                   float bottomRightRadius,
                                   int strokeWidth,
                                   @ColorInt int strokeColor) {
    GradientDrawable drawable = new GradientDrawable();
    drawable.setColor(color);
    drawable.setStroke(strokeWidth, strokeColor);
    drawable.setCornerRadii(new float[]{
            topLeftRadius, topLeftRadius,
            topRightRadius, topRightRadius,
            bottomRightRadius, bottomRightRadius,
            bottomLeftRadius, bottomLeftRadius
    });
    return drawable;
}
Copy the code

The other is the logic for coloring Drawable. This can reduce duplicate types of resources in your code, and it can also make your application more subject compatible,

// https://github.com/Shouheng88/Android-utils/blob/master/utils/src/main/java/me/shouheng/utils/ui/ImageUtils.java
public static Drawable tintDrawable(Drawable drawable, @ColorInt int color) {
    final Drawable wrappedDrawable = DrawableCompat.wrap(drawable.mutate());
    DrawableCompat.setTintList(wrappedDrawable, ColorStateList.valueOf(color));
    return wrappedDrawable;
}
Copy the code

In addition, various methods such as logic for conversion before various bases, vibration, and obtaining application information are added.

2.2 Kotlin’s Trick: Expand with Kotlin

Another big update is the addition of module: utils-ktx, which uses Kotlin’s new features to further wrap the utility classes to make calls cleaner. Of course, not everyone will accept this, so I split it into a separate Module and generate a separate dependency that the user is free to choose from.

The new functionality of utils-ktx can be described by two key words: enabling and global functions. Enabling is adding new methods to existing classes, such as String and Bitmap,

// Some new methods of the String class
fun String.isSpace(a): Boolean = StringUtils.isSpace(this)
fun String.isEmpty(a): Boolean = StringUtils.isEmpty(this)
// ...
// Some new Bitmap methods
fun Bitmap.toBytes(format: Bitmap.CompressFormat): ByteArray = ImageUtils.bitmap2Bytes(this, format)
fun Bitmap.toDrawable(a): Drawable = ImageUtils.bitmap2Drawable(this)
// ...
Copy the code

The other is the global function. We’ve already simplified the logic of retrieving resources by using the global Context, and now we can just call the following methods to retrieve resources,

@ColorInt fun colorOf(@ColorRes id: Int): Int = ResUtils.getColor(id)
fun stringOf(@StringRes id: Int): String = ResUtils.getString(id)
fun stringOf(@StringRes id: Int.vararg formatArgs: Any): String = ResUtils.getString(id, *formatArgs)
fun drawableOf(@DrawableRes id: Int): Drawable = ResUtils.getDrawable(id)
// ...
Copy the code

So, when we want to get a Drawable and color it according to the topic, we can just use the following code:

iv.icon = drawableOf(R.drawable.ic_add_circle).tint(Color.WHITE)
Copy the code

For example, when we want to request storage permission from the system in our Activity, we can do this with the following line of code:

checkStoragePermission { 
    / /... Add logic after request to permission
}
Copy the code

This is fine, of course, but the irony is that in the case of the new string.isspace () method, the logic for our new method is actually done via stringutils.isspace (this). When a method is added, the class instance of the new method is actually compiled as the first parameter of the static method. What we’re doing here is, the code gets transferred, and then it gets compiled and it gets transferred back. It’s just that the code looks simpler, at the expense of a few more compilations for no reason.

Another caveat is that Kotlin, while flexible, can clutter a project if left unchecked. For example, the new methods and global methods here are global in nature, and the introduction of these dependencies in a module has these new features. If everyone is randomly adding similar methods to the code, then obviously there will be all kinds of method and code conflicts. So, it’s fine to just use Kotlin as Java, but the use of new features must be constrained.

Kotlin’s flexibility is a double-edged sword, not just in the case of the new method described above. I also ran into a pit in development where I used Kotlin to define database objects, which IS covered here as well. I use Room as the database in the project. Room will automatically create database SQL based on database objects at compile time. This brings up the problem of null-type judgment. We know that database columns are categorized as NULLABLE and non-nullable. Room will compare the Schema used to create the database at compile time and find that the Schema is inconsistent, even if the null constraint is inconsistent, it will require you to do data migration, or throw exceptions. SQLite column update, a tedious item, needs to be deleted first and then added. Take the following database objects as an example,

@Entity
data class Repository(
    @PrimaryKey(autoGenerate = true) var id: Int? .var username: String,
)
Copy the code

If you define an object as a String username, you’re in trouble. This means that you have to ensure in your code that username is not null, and if it is null, KotlinNull will be thrown. And then one day, you hit on this problem and you want to change it to String? Type, sorry, can only do database migration. The column definition has gone from not NULL to nullable. I think one of the reasons for this may be that we’re used to Java development, where strings are nullable by default. String in Kotlin looks the same as in Java, but has a completely different meaning.

So, again, Kotlin is flexible, but he’s also flawed!

3, Android – UIX

The library’s address is: github.com/Shouheng88/…

3.1 Mocking Kotlin again

This library is a set of UI collection, I design it is used to do a standard UI library, in addition to the commonly used controls, I also hope that it can be part of the page as a control external exposure, so as to simplify my development of the workload. However, the library has not been publicized since it was developed, in part because I found it annoying to use some of Kotlin’s features.

Take BeautyDialog, a wrapper class for Android dialogs that I developed using Kotlin and built using the Builder pattern. What annoys me is that I think using the Builder pattern in Kotlin looks stupid! Kotlin provides a number of features that allow us to assign values to instances directly through “field references.” Kotlin’s elegance is completely lost when you use the Builder pattern. In addition, I usually choose to use some IDEA plug-in to assist in generating the code needed for the Builder pattern, but it is not available in Kotlin and must do this low-level work manually.

In addition, I usually use custom annotations instead of enumerations, but in Kotlin, applying custom enumerations to the WHEN statement lost its checking and “reminder” mechanism, forcing me to look at the enumeration definition to know which integer to use.

In addition, the use of other Kotlin features, such as specifying default parameters for methods, adds flexibility to the code but also causes some headaches. In the case of the constructor above, if the default argument of a method is treated as an assignment, there are actually multiple possibilities for assigning a value to a field of a class instance — it could be the default value of the method’s default argument, the default value of the constructor’s field, or the default value of the class instance’s field. More choices, more confusion.

That’s just the case when Kotlin calls Kotlin. If You call Kotlin’s methods in Java, how will the above Kotlin features, including the default methods of the interface, behave in Java? So using Kotlin can be a lot cleaner if you’re just doing business development, but this flexibility becomes a double-edged sword when you apply Kotlin to class library development.

However, some of Kotlin’s features do make it easier for us to do some things, like this one that prevents consecutive clicks:

// A custom OnClickListener
abstract class NoDoubleClickListener : View.OnClickListener {

    private var lastClickTime: Long = 0

    override fun onClick(v: View) {
        val currentTime = System.currentTimeMillis()
        if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {
            lastClickTime = currentTime
            onNoDoubleClick(v)
        }
    }

    protected abstract fun onNoDoubleClick(v: View)

    companion object {
        var MIN_CLICK_DELAY_TIME                       = 500L}}// Add a method to the View to replace setOnClickListener later
fun View.onDebouncedClick(click: (view: View) - >Unit) {
    setOnClickListener(object : NoDoubleClickListener() {
        override fun onNoDoubleClick(v: View) {
            click(v)
        }
    })
}
Copy the code

In fact, NoDoubleClickListener is an abstract class that can prevent continuous clicks, but using Kotlin’s features, we can add a method to the View and use Kotlin’s functional programming, It is much simpler to prevent consecutive clicks like this:

btnRateIntro.onDebouncedClick {
    // do something
}
Copy the code

3.2 OpenCV development environment

In this library, I recently added a module, the UiX-Image module. This module builds the OpenCV development environment and also contains the OpenCV extension library. In my application mobile toolbox, I used this module to achieve more than ten image effects including perspective transformation, mirror flip, emboss and so on. Open source code implements only two basic effects and tailored logic. If you want to learn how to use OpenCV in Android, you can also use this code

3.3 A little daydreaming

I still use BRVAH directly as an Adapter in my personal development projects. I have to say, this library works really well. Whenever I work on my own projects, I can’t help but think of my former colleagues who are still developing with native Adapter. A simple layout requires a lot of code, and it’s tempting to say, BRVAH works. However, I’ve recently found that by simply wrapping with generics and so on, it’s possible to not even declare Adapter everywhere,

We can define a tool method as follows,

object AdapterHelper {

    fun <ITEM> getAdapter(@LayoutRes itemLayout:Int,
                          converter: ViewHolderConverter<ITEM>,
                          data: List<ITEM>): Adapter<ITEM>
            = Adapter(itemLayout, converter, data)

    interface ViewHolderConverter<ITEM> {
        fun convert(helper: BaseViewHolder, item: ITEM)
    }

    class Adapter<ITEM>(
        @LayoutRes private val layout: Int.private val converter: ViewHolderConverter<ITEM>,
        val list: List<ITEM>
    ): BaseQuickAdapter<ITEM, BaseViewHolder>(layout, list) {
        override fun convert(helper: BaseViewHolder, item: ITEM) {
            converter.convert(helper, item)
        }
    }
}

// a custom method
fun BaseViewHolder.goneIf(@IdRes id: Int, goneIf: Boolean) {
    this.getView<View>(id).visibility = if (goneIf) View.GONE else View.VISIBLE
}
Copy the code

And then, whenever we need to get the Adapter we just do something like this,

adapter = AdapterHelper.getAdapter(R.layout.item_device_info_item,
    object : AdapterHelper.ViewHolderConverter<InfoItem> {
        override fun convert(helper: BaseViewHolder, item: InfoItem) {
            helper.setText(R.id.tv1, item.name)
            helper.goneIf(R.id.tv_name, TextUtils.isEmpty(item.name))
        }
    }, emptyList())
Copy the code

Along these lines, we could even hide the Adapter concept and implement a list of data directly through id and attribute bindings. Write so many years of Android, whether ListView or RecyclerView exist Adapter concept may become history ~

I have to sigh, Android development is really quite mature ~

conclusion

The above code base is open source, you can go to my Github above through the source code for further understanding. In addition to technical stuff, I occasionally write about non-technical stuff. Interested can pay attention to my public number ~