» » Database on SharedPreferences - Android Kotlin

Database on SharedPreferences - Android Kotlin

Android uses DataBase (SQLite, FireBase, etc.) to store a lot of information, and SharedPreferences is used to simply store settings. The question is, is it possible to use SharedPreferences as a kind of Database and how to do it?

The article is written about a training project, it is impossible to repeat this on the sale, at least it is unsafe. Not the best implementation of certain things is intentionally used so that it can be discussed in the comments.

What is this all for? What for?


Probably, the question arose in your head: “In what situations might you need to use SharedPreferences, if absolutely everything can be stored in the Database?”

I answer: indeed, absolutely all information can be stored in this form. Have a table with application settings (for example, dark theme: on / off), and in another table store the very information that the application itself displays. But is it effective? If there is a lot of information and SQL queries help you, then yes, but do not forget that Databases are in most cases a complex mechanism, which can reduce the performance of your application. If there is not much information, then it is much more profitable to use SharedPreferences.

Brief theory on how to use SharedPreferences


To get an instance of the SharedPreferences class, two methods are used in the application code:

  • getPreferences()— inside an activity to refer to an activity-specific preference,
  • getSharedPreferences()- inside an activity to refer to an application-level preference.


All of these methods return an instance of the SharedPreferences class, from which you can get information using a number of methods:

  • getBoolean(String key, boolean defValue),
  • getFloat(String key, float defValue),
  • getInt(String key, int defValue),
  • getLong(String key, long defValue),
  • getString(String key, String defValue).


The default is used MODE_PRIVATE- only the application has access to the settings:

  • MODE_APPEND- attaches new settings to existing ones,
  • MODE_ENABLE_WRITE_AHEAD_LOGGING,
  • MODE_MULTI_PROCESS,
  • MODE_WORLD_READABLE- allows other applications to read the settings,
  • MODE_WORLD_WRITEABLE- allows other applications to write new settings.


These configuration files are stored in the directory: /data/data/имя_пакета/shared_prefs/имя.xml.

Let's start the experiment


Imagine such a project: a json file is input, it stores information about currencies (name, cost, etc.). We need to save this data and, when the application is restarted, show it without reading json (all data is taken from our database). Possible interactions with the currency card: change any field.

All sources can be found on GitHub at the end of the article.

Let's create the most ordinary class and pass it to the context arguments for SharedPreferences. (I would like to hear your opinion on classes. Which class would you prefer to use in this situation?)

class DataBasePreferences(var context: Context)


We will create two spaces. The first will store information about our DB and will be static in size. It will store the number of our cards with currencies. The second field should store those same cards, which means it should be able to dynamically change its size.
How will it work? SharedPreferences stores data as, roughly speaking, a Map (key->value), and all information is in the "data table". Thanks to the name of this table, we will move through the cards. New table name = new card. We will call the tables numbers, they will serve as their id. This will make it much easier to navigate them.

Let's create and initialize global class variables:

private var prefs: SharedPreferences
private val prefsSetting = context.getSharedPreferences("databaseInfo", Context.MODE_PRIVATE)
private var editor: SharedPreferences.Editor
private val editorSettings = prefsSetting.edit()
private var indexNow = 0

init {
    prefs = context.getSharedPreferences(getSizeInt().toString(), Context.MODE_PRIVATE)
    editor = prefs.edit()


And here is our first function. It allows you to find out the size of 2 fields (how many tables it contains):

fun getSizeInt(): Int {
    return prefsSetting.getInt("size", 0)
}


If the field is not created, then 0 is returned.

The getter has been created, now it's time for the setter. To do this, we will create two functions:

private fun sizeUp() {
    editorSettings.putInt("size", getSizeInt() + 1).apply()
}

private fun sizeDown() {
    editorSettings.putInt("size", getSizeInt() - 1).apply()
}


ATTENTION: if you do not write .apply() at the end, the changes will not be saved!

Second area


Now let's move on to 2 areas, we need to be able to navigate through our "tables", for this we implement the following function:

private fun updatePrefs(index: Int) {
    if (indexNow != index) {
        indexNow = index
        prefs = context.getSharedPreferences(index.toString(), Context.MODE_PRIVATE)
        editor = prefs.edit()
    }
}


It's worth explaining the code a bit. In the arguments, we take the name of the table (its serial number), after which you can notice a check that compares the current serial number of the table with the received one, if they are not equal, the current table name is reassigned and it is opened. We get access to reading the table thanks to the prefs global variable, and to editing - editor.

Adding cards to the database


Well, I think we can proceed to filling in our table:

fun addJSONObject(_input: JSONObject) {
    sizeUp()
    updatePrefs(getSizeInt())
    setId(_input.getString("ID"))
    setNumCode(_input.getString("NumCode"))
    setCharCode(_input.getString("CharCode"))
    setNominal(_input.getString("Nominal"))
    setName(_input.getString("Name"))
    setValue(_input.getString("Value"))
    setPrevious(_input.getString("Previous"))
}


We get a JSON object as input, after which we increase the number of all our tables, open a new table and use setters. Below is the code for one of these setters:


fun setId(_input: String, _id: Int? = -1) {
    _id?.let {
        if (_id != -1)
            updatePrefs(_id)
        editor.putString("ID", _input).apply()
    }
}


There is no need for explanations here. The function takes 2 arguments, with the last argument being optional (if not changed, it will default to -1) and can also store null. Next comes the construction _id?.let{}, it allows you to run a piece of code (in curly braces) if the variable is not equal to null. Then there is a check whether the standard value of 2 arguments. If we received the table number at the input -> open the desired table. At the end, we assign a new value for the “ID” key and do not forget to apply all changes using .apply(). Why we added the ability to _idstore a variable null, I will explain a little later.

Similar setters need to be created for each JSON object key.

Reading a card


There are setters for individual keys, which means that our self-made database already knows how to save information, it remains to teach it to read. Below is an example of one of the getters:

fun getId(_id: Int? = -1): String? {
    _id?.let {
        if (_id != -1)
            updatePrefs(_id)
        return prefs.getString("ID", "").toString()
    }
    return null
}


We can see how the function can take a custom table name or use a default value. If the user did not enter a table name, then the information will be taken from the current one.

Card search


Great, our database on SharedPreferences is able to save and display data, though there is one unresolved problem. Suppose that when working with the application, the user wants to change a certain card by clicking on it, for this we already have setters, but how do we know which table needs to be opened for work? Suppose we have the opportunity to get some information from the card, for example, the abbreviated name of the currency. It turns out that we need to go through all existing tables, find a match and display the name of this table. This is a long operation, so it’s better not to create such situations, for example, let each card in the interface store its own local number, which will match the name of the table, but if this is not possible, then our new “heavy” function goes into battle:

fun searchIdCardByNumCode(_input: String): Int? {
    for (index in 1..getSizeInt()) {
        updatePrefs(index)
        if (_input == getNumCode()) {
            return index
        }
    }
    return null
}


If no match is found, null will be returned. Since this function is likely to be used in combination with setters and getters, we've added null support to them. This function looks for matches by NumCode value, and nothing prevents us from doing similar functions for the remaining keys.

DataBase on SharedPreferences in action


Let's go to our MainActivity and create an instance of our class, naming it db. Then we get a JSONobject from the Android memory and in the "createDB" function we write all the information we are interested in to our db, then we read some card information in the readInfoDB function, write it out, then change the information inside using the editInfoDB function and print the results again:

class MainActivity : AppCompatActivity() {
    private lateinit var db: DataBasePreferences

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        db = DataBasePreferences(this)
        createDB()
        val idTable = db.searchIdCardById("R01035")
        readInfoDB(idTable)
        editInfoDB(idTable)
        readInfoDB(idTable)
    }

    private fun createDB() {
        val jsonRoot = JSONObject(resources.openRawResource(R.raw.daily_json)
            .bufferedReader().use { it.readText() })
        val currency = jsonRoot.getJSONObject("Valute")
        val keys = currency.keys()
        for (index in 0 until currency.length())
            db.addJSONObject(currency.getJSONObject(keys.next()))
    }

    private fun editInfoDB(_id: Int?) {
        _id?.let {
            db.setCharCode("LP", _id)
            db.setName("@lonely_programmer")//Заметьте, я могу не указывать id явно, т.к. работаю с одной карточкой
            db.setNumCode("1234", _id)
        }
    }

    private fun readInfoDB(_id: Int?) {
        _id?.let {
            Log.d("checkDBCHARCODE: ", db.getCharCode(_id).toString())
            Log.d("checkDBNAME: ",db.getName(_id).toString())
            Log.d("checkDBNUMCODE: ",db.getNumCode(_id).toString())
        }
    }
}


Congratulations, it works! (PS lateinit in Kotlin is advised not to use)

 If suddenly Logcat does not work, try restarting it. (Usually this happens due to lack of computer power).

 

Outcome


What difficulties may arise in the future?

  • Deleting a table card, because this is not a linked list, all elements will be shifted when removed, and this, in turn, is a long process.
  • Adding a card not to the end of the list will shift all elements.
  • It is impossible to visually see what is stored in the Database.
  • Clearing the app cache will erase everything.
  • When replacing all table parameters, there is a possibility of losing it, because it is not known by what parameters to search for the table.
  • Very risky.

It was a fun and interesting experiment that really worked. Can it be used in sales? Of course not, let me remind you that this is a training project and this way of storing information is not secure, but I hope this article has shown you that absolutely everything is possible with due desire. That's the beauty of programming, you can do the same thing in different ways. If this topic seems interesting to you, let me know, I will do part 2, where we will add a lot of really cool features from older brothers and completely rewrite our DataBase. For example, we'll use encryption, a linked list, and let's create custom spaces. I promise it will be extremely interesting. Do not forget to share your opinion about this idea and the article in general.

If you have comments on the code - write, we will discuss in the comments.


sources

Related Articles

Add Your Comment

reload, if the code cannot be seen

All comments will be moderated before being published.