Getting started with C++17 mobile cross-platform development using Boden
TL;DR
- Love C++ and mobile apps? Learn how to write a simple cross-platform todo mobile app in idiomatic C++17 using the Boden Cross-Platform GUI Framework in this tutorial.
- The resulting app will use native machine code and native widgets to manage your todos in a simple single view application.
- Jump right to the boden-todomvc example repository on GitHub to have a look at the final code!
What will I learn?
In this tutorial you will learn how to write an app once in C++17 and then run it on an Android Phone and an iPhone from the same codebase.
Your app will:
- Allow users to manage a list of todos in a simple way. Users can add new todos, mark existing todos as completed, and delete todos using a swipe gesture. The app won’t lose the todo list when closed.
- It will look and behave natively since it’s going to use the native UI widgets of the platform.
- It will be based on the MVC architecture, similar to what you would write in Objective-C/Swift on iOS or Java/Kotlin on Android.
Setting up the project
The first step is to set up a new app project with the Boden Framework. Don’t worry, this will be quick and easy.
First, clone the boden
git repository from GitHub:
git clone --recursive https://github.com/AshampooSystems/boden.git
Make sure your system meets Boden’s requirements by following the Getting Started Guide on boden.io.
Finally, use the boden
command line tool to generate a new project:
cd boden
python boden.py new -n todomvc
That’s it!
The tool will create a folder titled todomvc
and generate the source and project files for a barebones Boden application, which we’ll modify and extend in the next part of this tutorial.
Setting up your development environment
Boden comes with support for Xcode on macOS and Android Studio on Linux, Windows, and macOS.
Change to the todomvc
directory and use the boden
command line tool to conveniently open the project in your IDE:
cd todomvc
python ../boden.py open
This will open Android Studio on Linux/Windows or Xcode on macOS.
In this tutorial, you will add several new source files to the project. You can simply add those files using the IDE. In this case, make sure the files are added to the source
directory.
An alternative way to add files is to create them in the source
directory using a text editor and then re-running python.py ../boden.py open
. This will automatically regenerate the project files with the new source files added.
Planning the todo app
When the todo app is finished, users should be able to:
- Write a new todo and add it to a list of items that need to be completed.
- Mark an existing todo in the list as completed.
- Delete a todo from the list.
The app should also persist the todo list to device storage so that todos won’t be lost when the app is closed and re-opened.
To keep things simple the app will be based on a single screen with a text field at the top and a scrollable list of todo items taking up the rest of the available screen space below. This is what it’s going to look like:
Planning the architecture
As mentioned above, this tutorial will stick to the well-known MVC pattern to structure the app’s architecture. Hence, the following components are needed to implement the full app:
- A data model of a todo. This is a simple data structure designed to store the information associated with a todo item in the list.
- A store component managing a list of todos and persisting that list to device storage.
- A list item view that presents a todo in the a view to the user.
- A data source accessing the store component to populate the list view.
- A view controller setting up the view, including a text field and a list view with the corresponding layout.
Creating the todo data model
The todo data model is a simple data structure consisting of a string containing the todo’s text and a bool indicating whether the todo is completed.
Create a new header file named Todo.h
and add the following code:
Creating the store
The store is responsible for maintaining a list of todos in memory and on device storage. It can add and remove todos from the list, and it can load and save the list to disk as a JSON file.
Create a new header file named TodoStore.h
and add the following code to declare the TodoStore
class.
Create the corresponding source file named TodoStore.cpp
.
In the first step, we’re going to include the necessary headers and implement the add()
and remove()
methods.
The add()
method appends an uncompleted todo containing the given todo text at the end of the todos
vector. The remove()
method removes the todo at the given index from the vector. Both methods call save()
to persist changes to the todo list immediately.
In the next step, we’re going to implement load()
and save()
to allow for storing and loading a list of todos from a JSON file.
Persisting the todo list
First, we’ll add a couple of new headers and using
statements for JSON, path retrieval, and filesystem functions at the top of TodoStore.cpp
.
- We use
nlohmann::json
which is bundled with Boden to parse and serialize JSON data. bdn::path
is a Boden module providing functions for retrieving common platform-specific directory paths, which we’ll use to retrieve a path to the documents directory.- Finally, we use
std::filesystem
for creating directories and safely concatenating path components.
In the next step, we’re going to implement the load()
and save()
methods.
Add the following code at the end of TodoStore.cpp
:
Here’s a short summary of what the functions do:
load()
usesstd::ifstream
to load thetodo.json
file from disk. It then parses the contents of the file usingnlohmann::json
and pushes it to thetodos
JSON array.save()
usesstd::ofstream
andnlohmann::json
to serialize the JSON data to thetodo.json
file.todoFilePath()
usesbdn::path::documentDirectoryPath()
andstd::filesystem
to safely create the document directory path for the application. It then returns the full path of thetodo.json
file on device storage.
As a final step, we are going to add to_json()
and from_json()
to the Todo
data structure defined above:
And create theTodo.cpp
source file containing the corresponding implementation:
Implementing the todo item view
TodoItemView
defines how the list item is going to be displayed on screen. It will be used by the list view data source to provide item views to the list view.
It inherits from bdn::ui::CoreLess
to indicate that it is a composite view without a native “view core”. This means that it does not wrap a native platform widget, but rather aggregates existing platform-independent views into one.
bdn::ui::ContainerView
is going to be the actual base class of TodoItemView
. This allows the class to manage child views in the view hierarchy.
Here’s what the TodoItemView.h
header file looks like:
bdn::Property
is used to make theTodo
data structure’s members available as data bindings.using CoreLess<ContainerView>::CoreLess
declares that theTodoItemView
class should reuseCoreLess
‘s constructor.void init() override
is required to implement the view’s custom initialization, where we’re going to set up its child views and layout.
In the next step, we’ll create a file named TodoItemView.cpp
and add the corresponding implementation:
- We override
void init()
to initialize the item view’s child views and layout. - The layout is defined by setting the
stylesheet
property to a suitable layout definition for the item view. - The rest of the implementation adds a checkbox for marking a todo as completed and a label for displaying a todo’s text accompanied with the corresponding property bindings and child layouts.
Implementing the list view data source
In the next step, we are going to implement TodoListDataSource
, which will be used by the todo app’s list view to retrieve instances of TodoItemView
.
The data source can be seen as a view model, fetching data from TodoStore
, populating a TodoItemView
with it, and then delivering it to a ListView
.
Create a new file named TodoListDataSource.h
and add the following declaration:
Now, create the corresponding source file named TodoListDataSource.cpp
:
TodoListDataSource
inherits from bdn::ui::ListViewDataSource
, which declares a couple of pure virtual functions that need to be overridden:
numberOfRows()
returns the number of rows to display in the list view.viewForRowIndex()
returns a reusable item view to be displayed at the given row index.heightForRowIndex()
returns the height of the view at the given row index.
Let’s a have a closer look at the implementation of viewForRowIndex()
.
viewForRowIndex()
is called by the framework with the listView
, rowIndex
, and reusableView
arguments. If a reusable view is not provided by the framework (reusableView == nullptr
), the implementation is responsible for instantiating a new item view:
if (!reusableView) {
// Create a new TodoItemView instance if none is
// provided by the framework
reusableView = std::make_shared<TodoItemView>();
}
The rest of the code populates the item view with data from the store and installs a listener on the completed
property to update the store when the user clicks the item’s checkbox:
auto item = std::dynamic_pointer_cast<TodoItemView>(reusableView);
item->text = _store->todos.at(rowIndex).at("text");
item->completed = _store->todos.at(rowIndex).at("completed");
std::weak_ptr<View> weakItem(item);
item->completed.onChange().unsubscribeAll();
item->completed.onChange() += [list=listView.get(), weakItem, this](const auto &property) {
if (auto rowIndex = list->rowIndexForView(weakItem.lock())) {
_store->todos.at(*rowIndex).at("completed") = property.get();
_store->save();
}
};return reusableView;
Setting up the app’s main view
Finally, we’re going to implement the app’s MainViewController
class.
The main view controller is instantiated by the framework upon app launch. It’s responsible for setting up the application’s window and principal view hierarchy.
MainViewController.h
and MainViewController.cpp
have already been generated by the boden
project generator tool. We’re now going to change and extend these files in order to set up the todo app’s main view.
This is the final MainViewController
declaration:
MainViewController
manages the following components:
- The application’s window.
- The text field users will use to enter the text for a new todo item.
- The list view for presenting todo items.
- A container view that contains the text field and the list view and acts as the content view of the window.
- An instance of
TodoListDataSource
which we created above to provide the list view with item views populated with data from theTodoStore
. - An instance of
TodoStore
itself responsible for managing the data model.
That’s it for the header.
Now, let’s have a look at the implementation in MainViewController.cpp
. The application’s main components are set up in the constructor of MainViewController
. First, we create the app’s window and set its title and layout:
In the next step, we set up the main container, text field, and list view:
Finally, we create an instance of TodoStore
, call load()
to load existing todos from disk, and create and wire an instance of TodoListDataSource
.
Wrapping up
In this tutorial, you’ve learned how to write a cross-platform todo list mobile app in C++17 using the Boden Cross-Platform GUI Framework.
If you want to play around with the project without writing the app yourself, take a look at the final code at the boden-todomvc example repository on GitHub.
If you want to learn more about Boden, check out our documentation at boden.io.
In the next tutorial, we’ll complete the app by setting it up for deployment to the Google Play Store and Apple iOS App Store.
What do you think?
What do you think about the tutorial and about the Boden framework? Feel free to offer your perspective and ideas in the comments section below.
If you enjoyed this article, feel free to hit that clap button 👏 to help others find it.