current position:Home>Android database master Secrets (10), how to better use litepal in kotlin

Android database master Secrets (10), how to better use litepal in kotlin

2022-01-27 05:02:21 Guo Lin

Preface

since LitePal stay 2.0.0 The version fully supports Kotlin after , I've been thinking about how to make LitePal Better integration and adaptation Kotlin Language , It's not just about support .

Kotlin It's really a wonderful language , There are many excellent features in it Java What can't be achieved in . therefore , stay LitePal Full support Kotlin after , I feel like if I still don't see these great features , Some of them are too violent . So in the latest LitePal 3.0.0 Version inside , I'm going to let LitePal Make better use of Kotlin Some of the language features of , It makes our development easier .

In addition to introducing LitePal 3.0.0 In addition to the upgrade content of the version , I'll also explain some Kotlin Advanced knowledge of .

First of all, let's look at how to upgrade .

The way to upgrade

Why is the version number span so large this time , Directly from 2.0 rose 3.0 Well ? Because this time LitePal There is a qualitative change in structure .

For better compatibility Kotlin Language ,LitePal Now it's no longer just a library , It became two libraries , Depending on the language you use , The libraries that need to be introduced are also different . If you're using Java, Then in build.gradle The following configuration is introduced in :

dependencies {
    implementation 'org.litepal.android:java:3.0.0'
}

 Copy code 

And if you're using Kotlin, Then in build.gradle The following configuration is introduced in :

dependencies {
    implementation 'org.litepal.android:kotlin:3.0.0'
}

 Copy code 

Okay , Let's take a look at LitePal 3.0.0 What did the version change .

Have to say , Actually LitePal The generic design of has never been very friendly , Especially when it comes to asynchronous queries , For example, let's look at the following code :

In asynchronous query onFinish() Calling back , What we get directly is not the query object , It's a generic T object , It also needs another forced transformation to get the object you really want to query .

If you don't think it's hard , Let's take a look at the following example :

You can see , This query returns a List<T>, We have to deal with the whole List Forced transformation . Not just one more line of code , The key is that the development tool will also give an ugly warning .

This design is not friendly in any way .

Thank you very much xiazunyang This friend is in GitHub It's put forward by Issue(github.com/LitePalFram… 3.0.0 Version's optimization of generics is largely based on his suggestions .

So let's take a look at , here we are LitePal 3.0.0 edition , How can the same function be written :

LitePal.findAsync(Song.class, 1).listen(new FindCallback<Song>() {
    @Override
    public void onFinish(Song song) {

    }
});

 Copy code 

You can see , Here it is FindCallback The generic type is declared on the interface as Song, So in onFinish() The parameters in the method callback can be specified as Song Type , Thus avoiding a cast .

So again , This can be done when querying multiple pieces of data :

LitePal.where("duration > ?", "100").findAsync(Song.class).listen(new FindMultiCallback<Song>() {
    @Override
    public void onFinish(List<Song> list) {

    }
});

 Copy code 

This time it's much fresher , stay onFinish() Callback method , What we got directly was a List<Song> aggregate , And there won't be that ugly warning again .

And if this code uses Kotlin To write the words , It's going to be more streamlined :

LitePal.where("duration > ?", "100").findAsync(Song::class.java).listen { list ->
    
}

 Copy code 

Thanks to the Kotlin Outstanding lambda Mechanism , Our code can be further streamlined . In the above code , At the end of the line list The parameter is the query List<Song> Set up .

So that's all about generic optimization , Let's look at another topic , Monitoring database creation and upgrade .

you 're right ,LitePal 3.0.0 Version of the new monitoring database creation and upgrade function .

This feature is added because JakeHao This friend is in GitHub The last one Issue(github.com/LitePalFram…

)

To implement this function, we must add a new interface , And I'm cautious about adding new interfaces , Because we have to consider the ease of use of the interface and the impact on the overall framework .

LitePal Every interface I try to make it simple and easy to use , So you can guess , It's very easy to monitor database creation and upgrade , It only needs a few lines of code to implement :

LitePal.registerDatabaseListener(new DatabaseListener() {
    @Override
    public void onCreate() {
    }

    @Override
    public void onUpgrade(int oldVersion, int newVersion) {
    }
});

 Copy code 

It should be noted that ,registerDatabaseListener() The method must ensure that it is called before any other database operation , And then when the database is created ,onCreate() Method will get a callback , When the database is upgraded onUpgrade() Method will get a callback , And tell you to tell you the old version number through the parameter , And the new version number after the upgrade .

Kotlin Version of the code is similar , But since this interface has two callback methods , So it doesn't work Kotlin The single abstract method of (SAM) This grammatical sugar , Only anonymous objects that implement the interface can be used :

LitePal.registerDatabaseListener(object : DatabaseListener {
    override fun onCreate() {
    }

    override fun onUpgrade(oldVersion: Int, newVersion: Int) {
    }
})

 Copy code 

In this way, we will monitor the creation and upgrade of the database, which is also a quick introduction , Next, we will enter into the main content of this article .

We can see from the above article that ,Kotlin Version of the code is generally more than Java Code should be simpler ,Google The official statistics given are , Use Kotlin Development can be reduced by about 25% Code above .

But it's about simplicity Kotlin, But there is one usage that makes me really uncomfortable . For example, use Java Inquire about song In the table id by 1 That's what this record says :

Song song = LitePal.find(Song.class, 1);

 Copy code 

And the same function in Kotlin But it needs to be written in this way :

val song = LitePal.find(Song::class.java, 1)

 Copy code 

because LitePal You have to know which table to query for data , So be sure to pass on a Class Parameters to LitePal Talent . stay Java We just need to pass in Song.class that will do , But in Kotlin The way of writing in becomes Song::class.java, Rather than Java The code is longer , Do you feel bad ?

Of course , Many people are used to writing , It's not a big problem . But as I learn more Kotlin after , I find Kotlin Provides a fairly powerful mechanism to optimize this problem , This mechanism is called generic materialization . Next, I will explain the concept and usage of generic materialization in detail .

To understand generic materialization , First you need to know the concept of generic erasure .

Whether it's Java still Kotlin, As long as it's based on JVM Language , Generics are basically implemented by type erasure . In other words, generic constraints on types exist only at compile time , The runtime cannot directly check generic types . for example , We create a List<String> aggregate , Although only string type elements can be added to the collection at compile time , But in the run time JVM You don't know what kind of elements it was meant to contain , It can only be recognized that it's a List.

Java The generic erasure mechanism of , Makes it impossible for us to use if (a instanceof T), Or is it T.class Such grammar .

and Kotlin Is based on JVM Language , therefore Kotlin The generics of are also erased at run time . however Kotlin Provides the concept of an inline function in , The code in an inline function is automatically replaced at compile time to where it is called , This makes the formal parameter declaration and argument passing when the original method is called , After compiling, it becomes a variable call within the same method . In this way, there is no generic erasure problem , because Kotlin After compilation, the generic part of the inline method is replaced with arguments directly .

In a nutshell , Namely Kotlin Allows the materialization of generics in inline methods .

Generic materialization

So how to write a generic to materialize it ? First , The method must be inline , It means to use inline Keyword to modify the method . secondly , Where generics are declared, you must also add reified Keyword to indicate that the generic is to be materialized . The sample code is as follows :

inline fun <reified T> instanceOf(value: Any) {

}

 Copy code 

Generics in the above methods T It's a materialized generic , Because it satisfies inline functions and reified The two prerequisites of keyword . So, with the help of generic materialization , What kind of effect can we achieve ? You can see from the method name , Here we use generics to implement a instanceOf The effect of , The code is as follows :

inline fun <reified T> instanceOf(value: Any) = value is T

 Copy code 

Although there is only one line of code , But here's an implementation of Java A completely impossible function in —— Determine whether the type of the parameter is a generic type . That's what makes generic materialization incredible .

So how do we use this method ? stay Kotlin You can write this in :

val result1 = instanceOf<String>("hello")
val result2 = instanceOf<String>(123)


 Copy code 

You can see , The generics specified in the first line of code are String, The argument is a string "hello", So the end result is true. The second line of code specifies that the generic is String, The parameter is a number 123, So the end result is false.

Besides being able to make type judgments , We can also get generic Class type . Take a look at the following code :

inline fun <reified T> genericClass() = T::class.java

 Copy code 

This code is even more incredible ,genericClass() Method directly returns the current specified generic class type .T.class This kind of grammar is in Java It's impossible , And in the Kotlin With the help of generic materialization, you can use T::class.java This kind of grammar .

Then we can call :

val result1 = genericClass<String>()
val result2 = genericClass<Int>()


 Copy code 

You can see , If we specify generics String, So, in the end, you can get java.lang.String Of Class, If generics are specified Int, In the end, you can get java.lang.Integer Of Class.

About Kotlin That's all for generic materialization , Now let's go back to LitePal above . So much about generic materialization , that LitePal How can we use this feature to optimize ?

Take a look back. , We checked just now song In the table id by 1 That's what this record says :

val song = LitePal.find(Song::class.java, 1)

 Copy code 

You need to pass in Song::class.java It's because you have to tell LitePal Go to query song The data in this table . And through the explanation of generics materialization , We know Kotlin You can use T::class.java This kind of grammar , So I am LitePal 3.0.0 This part of the feature is extended in , Allows you to declare which table to query by specifying generics . So the code can be optimized to look like this :

val song = LitePal.find<Song>(1)

 Copy code 

What about? , Do you feel that the code is refreshing a lot ? Looks like Java Version of the query to be more simple .

In addition, it benefits from Kotlin Excellent type derivation mechanism , We can also change the code to write as follows :

val song: Song? = LitePal.find(1)

 Copy code 

The two writing as like as two peas , Because if I was song The variable declares Song? type , that find() Method to automatically derive the generic type , So you don't have to do it manually <Song> The generic type of specifies .

except find() Out of the way , I'm still right LitePal Almost all of them are publicly owned API It's all optimized , As long as it's the original need to pass Class Parameter interface , I've added a generic to replace Class Parameter extension method . Be careful , Here I use the extension method , Instead of modifying the original method , In this way, you can use both , It's all up to you , If it is to modify the original method directly , Then after the project is upgraded, it may cause a large area of errors , It's something nobody wants to see .

So here I'll show you some other CRUD Use after operation optimization , For example, I want to use where You can write this when you query conditions :

val list = LitePal.where("duration > ?", "100").find<Song>()

 Copy code 

Here's at the end of find() The generic type is specified in the method <Song>, The result will be a List<Song> aggregate .

Want to delete song In the table id by 1 This data can be written as follows :

LitePal.delete<Song>(1)

 Copy code 

Want statistics song The number of records in the table can be written as follows :

val count = LitePal.count<Song>()

 Copy code 

Other optimization methods are similar , I believe you can draw inferences from one instance , I'm not going to show you one by one .

So we will LitePal The main functions in the new version are introduced . Of course , In addition to these new features , I also fixed some known bug, Improve the stability of the overall framework , If that's what you need , Then upgrade quickly .

copyright notice
author[Guo Lin],Please bring the original link to reprint, thank you.
https://en.cdmana.com/2022/01/202201270502167689.html

Random recommended