📜 ⬆️ ⬇️

Tai'Dzen: the first steps (part 3)

Dear habrasoobschestvu, welcome!
In this part of the article we will complete the study of a simple native application for maintaining a shopping list. I want to remind you that in the first part the structure of the project is considered, and the second part is devoted to some standard GUI elements. Today, the reader is waiting for work with the database, the organization of the search, as well as localization and other "final touches". All commits of the open git repository are provided with comments, for each stage in the text the corresponding tags are indicated. Welcome under the cut!

Part two

PART THREE

Tizen SDK offers a number of built-in tools for working with data. For simple tasks, which is ours, you can use ordinary read / write to the file. Working with files in detail, with examples described in the documentation . In cases requiring flexibility and large amounts of data, there is a mechanism for accessing the embedded SQLite database. It is useful to us for carrying out a quick search through the records and convenient reordering of the shopping list.

To work with the database there is a whole arsenal of tools. Tizen :: Io :: Database is designed to access the database: connection, direct execution of SQL queries, etc. Tizen :: Io :: DbStatement allows you to conveniently form a query and attach data to it: numeric, string, binary — of course, in the context of SQLite syntax. Tizen :: Io :: DbEnumerator is responsible for extracting data from the query result. This is the minimum set for working with the database. In most cases, I use this particular set, but under an additional samopisny shell that automates some routine operations. Also in Tizen SDK there are additional tools that facilitate the work, for example: SqlStatementBuilder - conveniently generates simple queries, Data controls - data exchange between applications, Tizen :: Io :: DataSet , Tizen :: Io :: DataRow - for working with tables in memory , etc.
')
Let's start from the stove, i.e. directly from a SQLite database file. You can create it when you first start the application (and the documentation describes in detail how to do this), you can also create a table using an SQL query. I prefer to use the already-formed SQLite file, with pre-prepared tables. For these purposes, it is convenient to use SQLite Manager , the plug-in for Firefox. The tool is cross-platform and quite functional. Those who have little opportunity, can use the console utility from the official site SQLite.

The new database file is saved in the data directory, which is the root directory of the Tizen project (see the first part of the article). We will need 2 tables: in one we will store purchases, and in the other - shopping lists:





As I already mentioned, in most projects for working with databases I use my own shell on 3 main classes: Tizen :: Io :: Database, Tizen :: Io :: DbStatement and Tizen :: Io :: DbEnumerator. The DataSet and DataRow classes are not mentioned in the documentation in the context of working with databases, except that they have the same root namespace Tizen :: Io. Not surprisingly, they were discovered by me after I wrote my own implementations (there is an excuse!). Anyway, they work, and you can see them in a commit under the label v0.3 . I want to emphasize that I wrote a shell for myself, and in no way pretend to the special elegance of code and architecture. Let us briefly list its main modules:

We now return to the variation of the MVC pattern discussed in the first part. The full implementation of the pattern implies that all its modules must somehow communicate with each other. This can be done directly through pointers, through an intermediary or through an event model, in general, there are many ways, with their pros and cons. In practice, I usually use a factory and an object manager, which allow objects to safely communicate with each other through a message interface. Of course, since the objects of the model and the controller will be present in a single copy, the first thing that comes to mind is to instantiate them through a singleton. The temptation is great, but in the real world the requirements for software are constantly changing, which means that any monoliths in the code will eventually become obsolete. I think this topic deserves separate consideration, and now, without being distracted by questions of architecture, we will place all the code in ShoppingListMainForm. I remind you, our project is educational!

The ShoppingListMainForm form is loaded once at the start of the application, and closes when it exits, so connecting / disconnecting the database is conveniently done in the appropriate event handlers:

result ShoppingListMainForm::OnInitializing(void) { result r = E_SUCCESS; //    … pDb = new DbAccess(); r = pDb->Construct(); if (IsFailed(r)) { AppLogDebug("ERROR: cannot construct DbAccess! [%s]", GetErrorMessage(r)); return r; } String strDbName = "lists1.sqlite"; r = pDb->Connect(strDbName); if (IsFailed(r)) { AppLogDebug("ERROR: cannot connect %S! [%s]", strDbName.GetPointer(), GetErrorMessage(r)); return r; } return r; } 

 result ShoppingListMainForm::OnTerminating(void) { result r = E_SUCCESS; r = pDb->Close(); if (IsFailed(r)) { AppLogDebug("ERROR: cannot close DbAccess! [%s]", GetErrorMessage(r)); return r; } delete pDb; pDb = null; return r; } 

In native development, Tizen does not use exceptions; instead, it is customary to return the resulting system enum and always check it. In case of an error, I also add a trace to the log. At first, it is very annoying, but it pays off when debugging. I generally forgot when I last used the debugger - thanks to the logs, you can always find out what is happening in the program. There are several commands for outputting messages to the log, but I recommend using AppLogDebug. First, it is deactivated in the release version, and the output of a large number of logs degrades performance. Secondly, Tizen itself pours tons of system messages into the log, and, as a rule, these messages are of type AppLogException:



In most cases, they are useless, so I turn them off and leave only my own debasement messages:



We see that I made a mistake - I specified the wrong file name. Replace lists1.sqlite with lists.sqlite, and the connection to the database is ready!

Now retrieve the names of the shopping lists. While there is nothing to extract, the database is empty, so in SQLite Manager we select the Lists table, go to the “View and Search” tab and add several test values:



To load the values ​​we need:

  1. DbDataSet table;
  2. the new class List is the structures for storing the rows of the Lists table;
  3. new class RowList, inheriting DbRow - provides access to the rows for DbDataSet;
  4. a class that inherits DbRowBuilderInterface - it will generate RowList objects for the DbDataSet;
  5. DbQuery - encapsulates a SQL query to the DbAccess database.


The form itself will inherit DbRowBuilderInterface. We will return RowList as a new row. If we only needed one column, we could use the ready-made DbRowValue class. When creating a table, do not forget to remember its identifier theDataSetIdGetLists - you will need it to determine which rows to pass to which table:

 void ShoppingListMainForm::GetLists() { DbDataSet theTable; theDataSetIdGetLists = theTable.GetId(); theTable.SetRowBulder(this); String strQueryString = "SELECT * FROM Lists"; DbQuery query; query.queryString = strQueryString; pDb->FillDataSet(query, theTable); int count = theTable.GetRowCount(); int valueInt; String* pvalueText; for (int i=0; i<count; i++) { DbRow* pRow = theTable.GetRow(i); if (pRow) { pRow->GetInt(0, valueInt); pRow->GetText(1, pvalueText); AppLogDebug("%i %S", valueInt, pvalueText->GetPointer()); } else { AppLogDebug("ERROR: pRow is null!"); } } return; } 

 DbRow* ShoppingListMainForm::BuildNewRowN(unsigned int tableId, unsigned int rowIndex, void* content) const { DbRow* pRow = null; if (tableId == theDataSetIdGetLists) { pRow = new RowList(new Content::List()); } return pRow; } 


Add a call to the GetLists method to the OnInitializing form initialization handler, launch the project and observe the id and list names in the log:

> 1 Gifts
> 2 Products
> 3 Walking equipment for the cat

What the project looks like at this stage - see the v0.4 label.

Now we will display the names of the lists in the ListView on the tab Tab1. For this, ShoppingListTab1 must have access to the data loaded from the database. If we had a full-fledged MVC, we would simply turn to the model and take everything we needed, and so we will have to sculpt a crutch:

  1. We make the pointer to DbAccess static and open for access from outside;
  2. The data extraction mechanism is transferred from ShoppingListMainForm to ShoppingListTab1;
  3. The connection to the database is transferred from ShoppingListMainForm :: OnInitializing to the beginning of ShoppingListMainForm :: Initialize, so that the base is connected before Tab1 is initialized.
  4. DbDataSet with lists is made a member of the ShoppingListTab1 class.

It turned out, to put it mildly, not very beautiful - this is the result of neglect of architecture. Let's continue: assign DbDataSet as a data source for ListView, and finally, we see the names of the lists on the screen:



What the project looks like at this stage - see the v0.5 label.

In the second part of the article, the context menu of list items was considered, where we placed the Delete button. It is time to use the event handler:

 void ShoppingListTab1::OnListViewContextItemStateChanged(Tizen::Ui::Controls::ListView& listView, int index, int elementId, Tizen::Ui::Controls::ListContextItemStatus status) { if (status == LIST_CONTEXT_ITEM_STATUS_SELECTED && elementId == ID_CNTX_BTN_DELETE) { //     RowList* pRow = dynamic_cast<RowList*>(theTableLists.GetRow(index)); if (pRow) { DbQuery theQuery; theQuery.queryString = "DELETE FROM Lists WHERE id = ?"; theQuery.AddParamInt(pRow->pList->id.value); ShoppingListMainForm::pDb->PerformRequest(theQuery); } if (pRow) { DbQuery theQuery; theQuery.queryString = "DELETE FROM Purchases WHERE list_id = ?"; theQuery.AddParamInt(pRow->pList->id.value); ShoppingListMainForm::pDb->PerformRequest(theQuery); } //      theTableLists.RemoveRow(index); //   pListview1->UpdateList(); } } 

In the database we delete the list itself and all its contents (purchases). After changes to the data source, you must also update the ListView so that the changes are displayed on the screen.

Potential trap: for the database file to be overwritten when the application is redeployed, it must be modified (or the application must be deleted from the target device). Otherwise, the old file will remain on the target device. This is done to quickly deploy the application so that you do not need to send megabytes of resource files each time.

Further work is more appropriate to track, studying the changes in the code.

Label v0.6 - the context menu (OptionMenu) with the "Add" command is attached to the left touch button. She invokes a dialog that prompts you to enter the name of the new item. After confirmation, the dialog is closed, and a new entry is added to the database and the DbDataSet table.

Tag v0.7 - implemented work with purchases. Selecting a list on the tab Tab1 leads to a tab Tab2, where the elements of this list are displayed. They can be edited: add, delete, mark completed. Completed purchases are displayed at the end of the list.

Label v0.8 - Search command added to the context menu. The search is carried out in a separate panel, looking for both the names of the lists and the names of purchases. Selecting a search result leads to the transition to the appropriate tab (and scrolling to the desired item, if necessary).

Label v0.9 - we bring beauty: we color the buttons, menus, panels using the recommended color palette and font sizes . Add text localization (Russian, English).



Finally, v1.0 is the final touch, namely, we compress the database with the VACUUM command when exiting the application.

At this our “Hello, world!” Can be considered complete. In the first part of the article, a variation of the MVC pattern was described, but its implementation was delayed to facilitate understanding (and presentation) of the material. The thing is useful and in the context of native Tizen-applications needs a separate consideration. In addition, Tizen SDK offers convenient built-in tools for communicating the GUI and main application code with data. I want to devote the following article to the description of these mechanisms. I hope it was not particularly tedious - I will be happy to answer questions :) May the power of Tai'Dzen be with you!

Source: https://habr.com/ru/post/213907/


All Articles