Project Setup and the TodoItem Model
Start a new MAUI app with dotnet new maui -n TodoApp, which scaffolds the platform heads and a default MainPage. Before touching XAML, define the domain model: a simple TodoItem class with an Id, Title, IsDone boolean, and DueDate. Keeping the model free of UI concerns (no INotifyPropertyChanged on the raw entity) lets you reuse it cleanly for persistence, while a separate observable wrapper handles UI binding.
Cricket analogy: Before a tournament begins, the groundskeeper prepares the pitch (project scaffold) long before any players (UI code) arrive, and the scorecard template (TodoItem model) is fixed before a single run is scored.
public class TodoItem
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public bool IsDone { get; set; }
public DateTime? DueDate { get; set; }
}Wiring Up MVVM Data Binding
The view model wraps TodoItem in an ObservableObject (from CommunityToolkit.Mvvm) so property changes automatically notify the UI, and exposes an ObservableCollection<TodoItemViewModel> for the list plus [RelayCommand] methods for AddTodo, ToggleDone, and DeleteTodo. The XAML CollectionView binds ItemsSource to that collection, using a DataTemplate with a CheckBox bound two-way to IsDone and a Label bound to Title, with compiled bindings (x:DataType) enabled for both the page and the DataTemplate to catch typos at build time.
Cricket analogy: The view model is like the scoreboard operator who watches every ball bowled (property change) and instantly updates the digital display (UI), rather than the crowd having to manually recalculate the score themselves.
<CollectionView x:DataType="vm:TodoListViewModel"
ItemsSource="{Binding Todos}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="vm:TodoItemViewModel">
<Grid ColumnDefinitions="Auto,*" Padding="10">
<CheckBox IsChecked="{Binding IsDone}" />
<Label Grid.Column="1" Text="{Binding Title}"
TextDecorations="{Binding IsDone, Converter={StaticResource BoolToStrikethrough}}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>Persisting To-Dos with SQLite
Add the sqlite-net-pcl NuGet package and create a TodoDatabase service that opens a connection to a file path from FileSystem.AppDataDirectory, calling CreateTableAsync<TodoItem>() once at startup. Wrap all inserts, updates, and deletes in async methods on this service, inject it via DI as a singleton in MauiProgram.cs, and call LoadTodosAsync from the view model's constructor or an OnAppearing-triggered command so the list survives app restarts.
Cricket analogy: SQLite persistence is like the official scorebook that survives after the day's play ends, so tomorrow's match can pick up historical stats, unlike a chalk scoreboard that's wiped clean overnight.
Call await Database.Init() once, guarded by a boolean flag or lazy Task, before any query — opening the SQLite connection is async and calling it redundantly on every page navigation wastes I/O.
Never store the SQLite database file inside the app bundle's read-only resources folder — always use FileSystem.AppDataDirectory, which is writable and correctly sandboxed per platform (Application Support on iOS, files/ on Android).
- Scaffold with
dotnet new maui, then define a plain TodoItem model decoupled from UI concerns. - Wrap the model in an ObservableObject-based view model for automatic UI updates via data binding.
- Use CollectionView with a DataTemplate and compiled bindings (x:DataType) for the to-do list.
- Use [RelayCommand] from CommunityToolkit.Mvvm to implement Add, Toggle, and Delete actions.
- Persist data with sqlite-net-pcl, storing the .db3 file under FileSystem.AppDataDirectory.
- Register the database service as a DI singleton so all pages share one connection.
- Initialize the database connection lazily/once to avoid redundant async overhead on every navigation.
Practice what you learned
1. Which NuGet package is commonly used for local SQLite persistence in a MAUI to-do app?
2. Where should the SQLite database file be stored in a MAUI app?
3. What CommunityToolkit.Mvvm attribute simplifies implementing ICommand for the AddTodo action?
4. Why enable compiled bindings (x:DataType) on the CollectionView's DataTemplate?
5. Why should the database service be registered as a singleton in DI?
Was this page helpful?
You May Also Like
MAUI Best Practices
Practical guidelines for structuring, optimizing, and shipping production-quality .NET MAUI applications.
MAUI Quick Reference
A condensed cheat sheet of core MAUI layouts, data-binding syntax, and Essentials APIs for quick lookup while coding.
MAUI vs Flutter
A technical comparison of .NET MAUI and Flutter across language, rendering, performance, and ecosystem to help you choose the right cross-platform stack.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics