SQLite In My Flutter App
Part of the ‘A Work in Progress’ Series
Data is very important. Every mobile app will be working with a data source in some form or another. It would be beneficial the framework used accommodates for this and clearly designates the code responsible for that data source. In this article, I’m going to introduce you to the code responsible for saving, changing, and deleting the very ‘todo’ items that are the purpose of the example app, WorkingMemory. More specifically, I’m going to present how the app first accesses and opens the data sources involved in this app.
A Work In Progress
This is part of the ‘A Work in Progress’ series of articles covering the progress of a simple ‘ToDo’ app I’m working on called WorkingMemory. The intent of this series is to document the implementation of every and all aspects of this app, its construction as well as its use of the MVC framework library package, mvc_application.
I Like Screenshots. Click For Gists.
As always, I prefer using screenshots over gists to show concepts rather than just show code in my articles. I find them easier to work with frankly. However, you can click or tap on these screenshots to see the code they represent in a gist or in Github. Ironically, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program on our computers — not on our phones. For now.
No Moving Pictures, No Social Media
There may be gif files in this article demonstrating aspects of the topic at hand. However, it’s said viewing such gif files is not possible when reading this article on platforms like Instagram, Facebook, etc. They may come out as static pictures or simply blank placeholder boxes. Please, be aware of this and maybe read this article on medium.com
Let’s start from the beginning, and when it comes to Flutter apps, that means starting in the function, main(). You can see the app, WorkingMemory, is being passed to the runApp() function as usual. If you’ve read past articles from this series, you know with regards to the MVC design pattern the function, createView(), returns ‘the View’ for the app as a whole. It’s not actually the ‘main screen’ for the app, but does concern itself with the setting up the app’s interface — it’s ‘look and feel.’ By convention, I always try to give it a name that describes its relationship to the particular app. Below, is a screenshot of the file, main.dart, and I’ve named the app’s View, WorkingView.
Let’s quickly walk through the code from this point and see when and where we’re first are introduced to the app’s data. As it’s traditional with apps following the MVC architecture, the app’s View is readily able to ‘talk to’ the app’s Controller, and the Controller is readily able to ‘talk to’ the app’s Model. You can easily switch out ‘talks to’ with ‘has access to’ if that makes more sense.
And so, in the screenshot below of the View, WorkingView, when this app starts up we can see this indeed holds true. When this app’s View is instantiated, it’s passed the app’s Controller using the named parameter, con.
As an aside, the second red arrow below points to another ‘View’ object. Any seasoned Flutter developer would recognize it as the Widget for the home route — it’s the home screen. Of course, in this MVC framework, its another View object since it involves the app’s interface. It’ll be the very interface that greets you when the app eventually starts up.
Who’s In Control?
A quick peek at the app’s Controller called, WorkingController, we see it instantiates yet another Controller in its constructor named, Controller. Now, this is the ‘home’ Controller. It’s for the ‘home’ Widget — the one named, TodosPage, and passed to the named parameter, home, in the screenshot above. The ‘home’ Controller is named, Controller, by convention. It’s an approach I take when the app I’m writing has only ‘one’ Controller for its ‘home screen.’ In other circumstances, it would be named much like the app’s Controller (i.e. WorkingController) — depending on specifics like the app’s purpose or the role of the ‘home screen.’
Regardless, let’s continue. The screenshot below is of the app’s Controller, WorkingController. In its constructor, you see a number of objects being instantiated including the ‘home’ Controller. You then see a function called, initAysnc(), calling those objects' own initAsync() functions. We’ll be talking about this shortly.
Control Your Model
Again, a Controller regularly ‘talks to’ a Model in the MVC design pattern, and, in this app, it’s no exception. In this app, the Model is first instantiated in the ‘home’ Controller’s constructor. A screenshot of that Controller is listed below. You can see I elected to use a static getter called, con, and make the Controller and consequently its model ‘globally available’ across the whole app. It was such a simple little program, that was my first implementation. However, I’ll likely forgo it now that I’ve made its repo publicly available to everyone and many are now following along. You see, it’s just not good programming practice to have ‘global’ access to such aspects of an app. Those getters will likely go away.
The Model Data
Anyway, back to it. The Model class is to contain and deal with all that is ‘data’ when it comes to the app. The Model class envelopes the database involved with a ‘curtain of abstraction.’ It stands between ‘the data’ and the rest of the app. The rest of the app doesn’t know, for example, how many data sources are actually involved in this app. It’s not one, not two, but three data sources (three databases) are opened when the Model class is instantiated. You can see that in the Model class’ constructor displayed below. It’s in the Model object’s own initAsync() function that those databases are then initialized and opened.
It Takes Time To Init
Now, performing such operations like opening a database may take a bit of time and so the initAsync() function returns a Future. Doing so allows the app to continue on while this particular operation gets on with opening the databases. Note, the MVC framework takes such ‘asynchronous’ operations into account, and that’s what I really want to talk about in this article.
Folders Mirror The Design
However, let’s take an aside for a moment and note the screenshot below includes the IDE’s project panel so you can see the directory structure containing the source code. That’s to show you there‘s some ‘meaning to the madness’ involved when it comes to where the code is situated. The class, Model, is essentially situated where it is used in the app. It’s found in a folder called, model, which in turn is found in a folder called, home, all under the standard Dart folder, src. This is intentional. Another developer can then approach the source code, and at a glance, determine that there‘s a database (or databases) involved in this app’s ‘home screen.’ Simple as that.
Note, Flutter’s own choice of words when naming its named parameters is mirrored in the directory structure of the source code. For example, the folder, home, is named after the named parameter, home, that takes in the ‘home screen’ called, TodoPages. The practice of separating the code by the three categories defined by the MVC design pattern is further reflected in the names of the source code’s folders. For example, you will consistently see folders named, controller, model, and view throughout the directory structure: model contains code regarding data, view for interface code and controller contains the app’s logic and event handling. This all makes finding the source code you what to work on at any given point in time that much easier. In the screenshot below, for example, you know all the source code under the folder, model, involves the app’s data — under the home screen. Nice.
Build Your Future
Ok, let’s step back a bit and note it’s the widget, FutureBuilder, that is used by the framework to set up and ready the app before it continues with its intended purpose. Below is a screenshot of the framework’s class called, App. It is there where the FutureBuilder is implemented. If you remember, the class, WorkingMemory, extends the class, App, back in the main.dart file.
You can see the initial data value for the FutureBuilder is a boolean set to false, and its Future object comes from a function also called, initAysnc(). Of course, it’s assumed you’re familiar with the FutureBuilder, and so you will understand that while this initAsync() function is running, a loading screen or the typical ‘circular progress indicator’ is displayed in the centre of the screen. When the initAsync() function is finished, it’s to return with the boolean value, true, so that the app can then continue. The contents of the framework’s initAsync() function are highlighted by the second red arrow below.
Init The Framework
Note the third red arrow in the screenshot above. The third red arrow is pointing at the app’s View calling its own initAsync() function. In this particular app, we know it by the name, WorkingView. Note, that in this framework, every View and Controller object each have their own initAsync() function, and in every View object, when its initAsync() function is called, it will then call all its associated Controllers’ initAsync() functions. A quick peek below at the core MVC library package, mvc_pattern, that contains the class, StateMVC, (the View component of this framework) will confirm this.
And so, we’ll go back to our Controller, and look at what’s in its initAsync() function — it’s a call to the Model class and its own initAsync() function. I’ve named that function in the Model class, initAsync(), just to retain some consistency. It’s for the benefit of some other developer ‘down the road’ in this app’s lifecycle to then know the purpose of that function at a glance.
And so, looking back now at the Model class below and its initAsync() function, you now realize all the databases are opened in the framework’s FutureBuilder widget before the app proceeds on to its main screen. It’s set up in that fashion so a developer using this MVC framework can place heaven knows what in any number of Controllers and their initAsync() functions. It’s the means to perform any and all asynchronous operations that need to be performed before the app can continue with its intended programming. In this particular case, it’s to open up a number of databases. Nice.
An SQLite Database
One of those databases instantiated and opened in the Model class is an SQLite database. It’s the class, ToDo, and you can see below its parent class is called, SQLiteDB. You’ll notice that it instantiates again in its own constructor the two other databases already instantiated in the Model class’ constructor!? Why does it do that? Obviously, it needs references to them as well. We’ll look at the reason for this another time. For now, let’s further look at this class.
In the Model class’ initAsync() function, this class has its own init() function called. It’s listed below, and you can see it overrides the parent class’ own init() function. It’s in this init() function, we see it defines some instance variables with particular Strings of particular interest. It’s also assumed you’re familiar with SQL, and so you’ll recognize those Strings as SELECT statements to, of course, retrieve data from the SQLite database. You can also see it gets the ‘key field’ for a particular data table in the database. Remember, this is all being done in the framework’s FutureBuilder.
Note, it’s in the parent class, SQLiteDB, and its own init() function where indeed the database is opened. That’s apparent in the screenshot of the parent class below.
So, there you have it. The data for the app has been accessed and opened at this point. It was my intent in this article to trace up to this point when and where the data for the app is accessed and opened. Granted, we’ve traced back to only one particular database leaving three more data sources for another day including another SQLite database as well as a Firebase database. However, I like to think you now have an appreciation of how the framework will accommodate apps when they have to perform a number of operations before it’s ready for the user.
Now, if you’ve time, let’s get back to the Model.dart file containing the ToDo class and quickly look at its remaining code. Knowing the class, ToDo, extends the parent class, SQLiteDB, let’s continue and see what’s involved when working with an SQLite database. Note, the class, SQLiteDB, comes from the library package, dbutils, which in turn works with the plugin, sqflite. It is the plugin that, in fact, ‘talks directly’ to the SQLite database. The library package, dbutils, merely makes common operations involving an SQLite database easier to perform.
In the class, ToDo, we see the function, onCreate(), is overridden to explicitly create the data table, working, when the app is first installed and started up in the user’s phone. In fact, there’s a second data table under the class, _IconFavourites, also created at the time. We’ll discuss that shortly.
After the database is configured and the data tables created and opened, we see the ToDo class then provides a list of functions that retrieve data (if any) from the data table. Below, you’ll recognize the SELECT statements defined earlier — each performing a specific query.
What follows the class, ToDo, in this dart file, Model.dart, is the class, _IconFavourites, and then that’s it for the file, Model.dart. This second class represents that the second data table opened in the onCreate() function listed above. It stores the user’s ‘favourite icons’ when using the ‘ToDo’ app (Of course, that’s another story). The class is self-contained. It too has its own SELECT statement. It even has the routine, saveRec(), that saves the next ‘favourite icon’ selected by the user. Again, the Model.dart file is to deal with the app’s data — whatever that may be now or into the future.