preface

LitePal’s internal API has long supported transaction functionality because the data manipulation is atomically ensured and partial success and partial failure are not allowed.

Good morning everyone, LitePal has been updated for two years!

Yes, I looked at the time, the last version of LitePal was released in October 2018, and it hasn’t been updated since. Because I spent most of my time working on The Giffun project, and giffun was working on The third Line of Code, I didn’t have the time or energy to maintain LitePal.

During this period, many friends asked me whether I had given up maintaining LitePal. Somehow I feel a little sad, I owe this project a little too much.

So what has changed about LitePal in its latest update two years later? Let’s take a look.

The change of LitePal

In the past two years, I not only didn’t have time to update LitePal functions, but also didn’t have time to pay attention to issues on GitHub, so I accumulated a large number of issues. Before developing new features, the first thing to do is to address these issues.

After reviewing all issues, I find that they can be classified into the following categories:

  1. Usage advice. I have replied to most of these issues, but I replied a little too late, so I may not be able to help you. I’m really sorry.

  2. Functional suggestions. Over the years, many friends have offered advice on the functionality of LitePal, which has made it even more powerful. But I’ll come back to the feature suggestions in a minute, so I’m going to skip this.

  3. System type bugs. Some friends use LitePal and get a crash and think it’s a bug with LitePal, but sometimes it’s not. For example, the CursorWindows bug has been mentioned several times, but it is actually a limitation of the system, CursorWindow cache data reaches the maximum limit will throw an exception. Even if you don’t use LitePal, SQLiteDatabase will still have this exception, so I really can’t fix this problem, and you should try to minimize loading a lot of data at once at the use level.

  4. LitePal Bug. Thank you very much for asking this question, this time I did find a few bugs inside LitePal. Such as the loss of data when upgrading the database in certain cases, the inability of the Date type field to hold data before 1970, the findFirst() method to query very slowly in some cases, and so on. This time, I fixed all of these proposed bugs before developing a new version to ensure a more stable version.

So how many issues are left on LitePal’s GitHub? Let me show you:

That’s right. There’s only one left. And it’s a suggestion for a new feature that I do plan to consider in a later release, so I’m keeping it for now.

Now that the Issues are resolved, we can finally upgrade LitePal.

In the previous version of LitePal 3.0.0, I changed one library to two in order to support some of the nice syntactic features of Kotlin, as shown below:

Yes, I thought it would be a good thing to introduce libraries in the same programming language, but I soon regretted it. It was a very bad decision.

After splitting the libraries into Java and Kotlin versions, they together introduce Core libraries, where the main business logic is implemented, as dependencies. So when I need to add something new, I need to do it in the Core library, add an external interface to the Java library, add an external interface to the Kotlin library, and add an external interface to the Kotlin specific syntax. Code that had to be maintained in one place now has to be maintained in four places, and the number of apis has quadrupled, causing code maintenance costs to increase dramatically.

This is a problem I have to solve, otherwise LitePal will become more and more difficult to maintain in the future. So, in the latest Version of LitePal 3.1.1, there is no longer a distinction between the Java and Kotlin versions, but a unified library. To upgrade LitePal to version 3.1.1, use both Java and Kotlin languages by declaring the following dependency library addresses:

Dependencies {implementation 'org. Litepal. Guolindev: core: 3.1.1'}Copy the code

When the two are combined, a lot of redundant code can be removed, and maintenance costs plummet. As for how this is done, it’s largely thanks to the bintray-Release library (github.com/novoda/bint… Before jCenter, the dependencies of the current project will be resolved, and then the project needs to rely on which library declaration in the POM file. For example, the POM file for LitePal 3.1.1 looks like this:

If you do not have these dependencies in your current project (for example, if you are developing in Java), Gradle will automatically download these dependencies. To ensure that LitePal can be normal operation.

Instead of providing two versions of the library specifically for Java and Kotlin, you have one piece of code that is compatible with both languages, and everyone is happy.

Here I want to go back to the feature suggestion.

LitePal has been a relatively niche open source library since its birth. Because the demand for mobile databases is not particularly strong, and LitePal is not the best mobile database framework, it is not likely to be accepted by everyone.

But there are a lot of Android developers who love LitePal because it’s so simple and easy to use that they don’t have to write a lot of code. I’ve had a lot of suggestions from passionate friends to add features like x and x to make the library even more powerful.

I am particularly grateful to my friends for their suggestions, and I can say that to a large extent, the iterations of LitePal have been based on your suggestions.

But after so many iterations, I look back and wonder if every piece of advice was worth taking. It’s a question mark.

Since it is a niche open source library, the suggestions themselves may not be many, so I am willing to listen to them and add to them. But after years of adding things up, I’ve realized that some of the advice doesn’t really make sense and isn’t needed by most developers. Adding these features can also make LitePal unstable or more difficult to maintain.

So, this time I decided to subtract LitePal.

After careful consideration, I decided to cut out the following three sections in stages.

1. Binary data storage

This feature is one I really shouldn’t have added, since databases are inherently unsuitable for storing binary data. Why is that? Binary data is often very large, and a high definition image can take up a few megs of memory. Storing this data in a database is dangerous, and may cause the CursorWindows error mentioned earlier to crash the program, which makes LitePal unstable.

How many developers have a need to store binary data in a database? This is really rare, because what most people do is store binary data locally as a file, and then store a path to the file in the database. This approach is more scientifically secure and does not put additional pressure on the database.

Therefore, as of Version 3.1.1 of LitePal, the ability to store and read binary data will no longer be supported (byte array fields defined in entity classes will be ignored). This change takes effect immediately. If this feature is useful, please complete the change before upgrading.

2. Perform asynchronous operations

Database operations need to be done asynchronously, which is a highly recommended behavior because database operations are inherently time-consuming.

However, because database operations need to be performed asynchronously, does that mean that the database framework needs to provide asynchronous functionality? I used to think so, so I added a lot of asynchronous interfaces to LitePal, but now I realize I did it wrong again.

Because in addition to database operations, there are many other time-consuming operations that need to be performed asynchronously. There are plenty of apis and open source libraries for implementing asynchronous functionality, such as Java thread pools, RxJava, coroutines, and so on. So LitePal really shouldn’t have that responsibility, and there are more appropriate frameworks that deal with that. For example, Google’s Room doesn’t provide an asynchronous database interface at all, but by default Room also forces you to perform database operations on a non-primary thread or crash.

In addition, LitePal’s asynchronous operation interface is really poorly designed, resulting in high maintenance costs. For example, I have a find interface for querying data, so to be able to query data asynchronously, I provide a findAsync interface. There is a delete interface for deleting data, and to do so asynchronously, I provide a deleteAsync interface. Do you see the problem? I doubled the number of apis to provide asynchronous operations, and with the addition of the Java and Kotlin versions of the library, I quadrupled the NUMBER of apis on top of the doubling, and the maintenance cost increased exponentially.

So, in terms of asynchronous operations, I’m going to continue to do the subtraction. LitePal doesn’t take on the extra work of asynchronous processing, but it doesn’t force developers to operate the database on a non-primary thread like Room does. Whether to operate the database on the main thread or the non-main thread is up to everyone to choose. If you already use RxJava or coroutines in your projects, asynchronous processing is easy enough for you to do without using the asynchronous interface provided by LitePal.

Due to compatibility with the old project, this change does not take effect immediately. Currently, all asynchronous interfaces have been marked as obsolete, but in the next version they will be completely removed, so please do not continue to use these interfaces.

3. Database storage location

In version 1.6.0, LitePal introduced the function of storing the database to an external SD card, mainly for the convenience of debugging programs. However, this behavior is extremely dangerous and can greatly affect the security of the application, because anyone can change the data in the database at will.

I also thought a lot about whether this feature should go or stay. On the one hand, I felt that even Google’s official database frameworks like Room didn’t offer the ability to store the database on an external SD card, so why should LitePal do it? On the other hand, the database is hard to debug and this is a real pain point for developers.

After careful consideration, I’ve decided to keep this feature for the time being, but will eventually remove it as the development debugging environment becomes more developed (for example, database debugging has been introduced in Android Studio 4.1).

If you’ve ever used LitePal, it’s very easy to store a piece of data in a database in LitePal. You just need to call the following code:

Person person = new Person(); person.setXXX(...) ; . person.save();Copy the code

The save method is an interface provided by LitePal, which parses the data, fields, associations and other information contained in the current object, and then stores the parsed data into the corresponding columns of the database table.

Storing a piece of data is written this way, so what do I do if I want to store data in a set? Of course you could write:

List<Person> personList = ...
for (Person person : personList) {
	person.save();
}

Copy the code

Once we have a collection, we just need to loop through the collection, calling the save method on each Person object.

But as mentioned earlier, LitePal’s save method parses the data, fields, associations, and so on contained in the current object. You’ll notice that in addition to the fact that the data changes, information like fields and associations is the same for every object, so parsing this information every time you loop will definitely increase the storage time.

For this purpose, LitePal provides a saveAll method for storing collection type data. For example, to implement the same function above, we can also write:

List<Person> personList = ...
LitePal.saveAll(personList);

Copy the code

Both methods do exactly the same thing, but the saveAll method only parses the fields and associations in the Person object once, so storage efficiency is greatly improved.

However, the saveAll method also has a disadvantage. What if some data stores succeed and some data stores fail in a set of stores? Note that the saveAll method does not return a value.

To handle this, version 3.1.1 of LitePal added the return value of the saveAll method.

The saveAll method returns true and false. True means that all data in the collection is stored in the database. False means that an exception occurred in the stored procedure and no data is stored in the database. Yes, the saveAll method internally starts transactions, so either all stores will succeed or all stores will fail, and no partial stores will succeed. This avoids many misunderstandings when using saveAll methods.

In addition, in version 3.1.1, I also provided a special syntax sugar for Kotlin’s saveAll method. If your project is using the Kotlin language, you can call the saveAll method as follows:

val personList: List<Person> = ...
personList.saveAll()

Copy the code

Obviously, this is a much cleaner way of writing.

LitePal’s internal API has long supported transaction functionality because the data manipulation is atomically ensured and partial success and partial failure are not allowed.

However, LitePal has never provided an external transaction interface before, but there is a real need for transactions from developers.

In the most common example of a transaction, you’re developing a transfer feature that subtracts a certain amount from one account and adds the same amount to another. The whole operation must be atomic, that is, either succeed or fail at the same time. If part of it is successful, the total amount of the account will not match after the transfer.

To this end, support for a transactional interface was finally added in Version 3.1.1 of LitePal, and its use is fairly simple, as it is almost identical to the transactional interface provided in SQLiteDatabase.

When we want to perform a set of database operations that either succeed or fail at the same time, we can write:

try {
	LitePal.beginTransaction();
	boolean result1 = 
	boolean result2 = 
	boolean result3 = 
	if (result1 && result2 && result3) {
		LitePal.setTransactionSuccessful();
	}
} finally {
	LitePal.endTransaction();
}

Copy the code

As you can see, the beginTransaction method is called to start the transaction, the endTransaction method is called to end the transaction, and all the database operations in between are in the transaction. If all operations are successful, then we can call the setTransactionSuccessful method before ending the transaction so that all operations are in effect. Otherwise, everything will be rolled back as if nothing ever happened.

Transactions are that simple to use. However, in Kotlin, transactions are even simpler because I have provided a kotlin-specific transaction API, which is written as follows:

LitePal.runInTransaction {
	val result1 = 
	val result2 = 
	val result3 = 
	result1 && result2 && result3
}

Copy the code

To explain briefly, we can pass a Lambda expression to the runInTransaction method, and all the code in the expression will run within the transaction. This syntactic feature takes advantage of Kotlin’s higher-order functions. I talked a lot about higher order functions last time on the air, and the third Line of Code is very comprehensive about this.

The last line of the Lambda expression requires a Boolean value to be returned, indicating whether all the database operations were successful. The database operations in the transaction will only take effect if they return true, and all operations will be rolled back if they return false or if an exception occurs.

That’s all there is to LitePal 3.1.1 update, but this article is for people who already have a basic understanding of LitePal to help them quickly upgrade to 3.1.1. If you haven’t worked with LitePal before, you can read my technical column, The Android Database Master’s Secrets, for a very detailed explanation of how to use LitePal.

LitePal’s open source library address is:

Github.com/LitePalFram…

If you want to learn about Kotlin and the latest on Android, check out my new book, First Line of Code Version 3, here for more details.

Follow my technical public number, every day there are quality technical articles push.

Scan the qr code below to follow wechat: