Создание приложений Flutter, работающих оффлайн
FlutterPulseЭта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Многие мобильные приложения разрабатываются с предположением, что пользователь всегда будет подключен. Но в реальном мире сетевое подключение часто ненадежно…
Многие мобильные приложения разрабатываются с предположением, что пользователь всегда будет подключен. Но в реальном мире сетевое подключение часто ненадежно: самолеты, метро, сельские районы или просто плохой Wi-Fi. Вот почему offline-first design так важен. В Flutter реализация режима оффлайн проста, если вы заранее планируете это в своей архитектуре.
Почему важен режим оффлайн
- Пользовательский опыт → Приложение не должно казаться "сломанным" без интернета.
- Целостность данных → Пользователи не должны терять данные, созданные в оффлайн-режиме.
- Удержание → Приложения, которые грамотно обрабатывают оффлайн-режим, вызывают больше доверия.
Основная идея
Режим оффлайн требует трех вещей:
- Локальное хранилище для кэширования данных.
- Стратегия синхронизации для приведения локальных и удаленных данных в соответствие.
- Чистая архитектура которая разделяет локальные и удаленные источники.
Архитектура
Один из распространенных паттернов — Repository pattern:
abstract class UserRepository {
Future<List<User>> getUsers();
Future<void> addUser(User user);
}Реализация:
class UserRepositoryImpl implements UserRepository {
final RemoteDataSource remoteDataSource;
final LocalDataSource localDataSource;
UserRepositoryImpl(this.remoteDataSource, this.localDataSource);
@override
Future<List<User>> getUsers() async {
try {
final remoteUsers = await remoteDataSource.getUsers();
await localDataSource.cacheUsers(remoteUsers);
return remoteUsers;
} catch (e) {
// Fallback to cached data
return await localDataSource.getCachedUsers();
}
}
@override
Future<void> addUser(User user) async {
await localDataSource.addUser(user);
try {
await remoteDataSource.addUser(user);
} catch (_) {
// Mark as "pending sync"
await localDataSource.markPending(user);
}
}
}Варианты локального хранилища в Flutter
- SQLite (sqflite / drift) — структурированные данные, запросы, индексация.
- Hive — легковесное, быстрое, хранилище в стиле NoSQL.
- SharedPreferences — только для небольших данных в формате ключ-значение.
Пример с sqflite:
class LocalDataSource {
final Database db;
LocalDataSource(this.db);
Future<void> cacheUsers(List<User> users) async {
final batch = db.batch();
for (var user in users) {
batch.insert('users', user.toJson(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
await batch.commit(noResult: true);
}
Future<List<User>> getCachedUsers() async {
final result = await db.query('users');
return result.map((e) => User.fromJson(e)).toList();
}
Future<void> addUser(User user) async {
await db.insert('users', user.toJson());
}
Future<void> markPending(User user) async {
await db.update('users', {'pending': 1},
where: 'id = ?', whereArgs: [user.id]);
}
}Стратегия синхронизации
Когда подключение восстановлено:
- Проверьте ожидающие операции в локальной БД.
- Отправьте их на сервер.
- Пометите их как синхронизированные после подтверждения.
Пример:
Future<void> syncPendingUsers() async {
final pendingUsers = await localDataSource.getPendingUsers();
for (var user in pendingUsers) {
try {
await remoteDataSource.addUser(user);
await localDataSource.markSynced(user.id);
} catch (_) {
// Сохранить как ожидающий
}
}
}Лучшие практики
- Всегда сначала читайте из кэша, затем обновляйте удалёнными данными, если они доступны.
- Сохраняйте флаг "ожидающий" для несинхронизированных операций.
- Пакетное обновление при синхронизации для уменьшения количества вызовов API.
- Разрабатывайте API-эндпоинты с учётом синхронизации (идемпотентные, способные обрабатывать повторные попытки).
Заключение
Режим оффлайн — это не просто функция, а ожидание. Flutter делает это относительно простым с помощью инструментов, таких как sqflite, drift, и Hive. Сочетая чёткий шаблон репозитория, локальный кэш и логику синхронизации, вы можете создавать приложения, которые остаются надёжными независимо от подключения.