Loading a database on mobile
Before continuing the UI implementation, we have to take care of the database deployment on mobile. Spoiler: this will not be fun.
We have to jump back to DatabaseManager.cpp in the gallery-core project:
DatabaseManager& DatabaseManager::instance() { return singleton; } DatabaseManager::DatabaseManager(const QString& path) : mDatabase(new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE"))), albumDao(*mDatabase), pictureDao(*mDatabase) { mDatabase->setDatabaseName(path); ... }
Whereas on Desktop, the SQLite3 database is created at the instruction mDatabase->setDatabaseName(), on mobile it does not work at all. This is due to the fact that the filesystem is very specific on each mobile platform (Android and iOS). An application has only access to a narrow sandbox where it cannot mess with the rest of the filesystem. All the files inside the application directory must have specific file permissions. If we let SQLite3 create the database file, it will not have the right permission and the OS will block the database from opening.
As a consequence, the database will not be properly created and your data cannot be persisted. When using the native API, this is not a problem since the OS takes care of the proper configuration of the database. Because we are developing with Qt, we do not have easy access to this API (except by using JNI or other black magic). A workaround is to embed a "ready-to-use" database in the application's package and copy it at the right filesystem path with the correct rights.
This database should contain an empty created database without any content. The database is available in the source code of the chapter (you can also generate it from the source code of Chapter 12, Conquering the Desktop UI). You can add it to the gallery.qrc file.
Because our layers are clearly defined, we just have to modify the DatabaseManager::instance() implementation to handle this case:
DatabaseManager& DatabaseManager::instance() { #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) QFile assetDbFile(":/database/" + DATABASE_FILENAME); QString destinationDbFile = QStandardPaths::writableLocation( QStandardPaths::AppLocalDataLocation) .append("/" + DATABASE_FILENAME); if (!QFile::exists(destinationDbFile)) { assetDbFile.copy(destinationDbFile); Qfile::setPermissions(destinationDbFile, QFile::WriteOwner | QFile::ReadOwner); } } static DatabaseManager singleton(destinationDbFile); #else static DatabaseManager singleton; #endif return singleton; }
We first retrieve the platform-specific path of the application with a nifty Qt class: QStandardPaths. This class return paths for multiple types (AppLocalDataLocation, DocumentsLocation, PicturesLocation, and so on). The database should be stored in the application data directory. If the file does not exist, we copy it from our assets.
Finally, the permissions of the file are modified to ensure that the OS does not block the opening of the database (due to permissions not being restrictive enough).
When everything is done, the DatabaseManager singleton is instantiated with the correct database file path and the constructor can open this database transparently.
These workarounds were not trivial. This shows the limitations of a cross-platform application on mobile. Each platform has its own very specific way of handling the filesystem and deploying its content. Even if we manage to write platform agnostic code in QML, we still have to deal with differences between the OSes.