Рубрики
Flutter

Flutter 3.27 Migration Guide: Как обновить проект

Миграция на новую версию Flutter — это ответственный процесс. В этой статье пошагово разберём как безопасно обновить проект на Flutter 3.27.

Перед началом

  1. Сделайте бэкап проекта
  2. Прочитайте changelogpub.dev
  3. Проверьте зависимости — совместимость с Flutter 3.27

Шаг 1: Обновите Flutter SDK

flutter channel stable
flutter upgrade
flutter --version  # Должно быть 3.27.x

Шаг 2: pubspec.yaml

Обновите environment:

environment:
  sdk: '>=3.4.0 <4.0.0'

Шаг 3: flutter pub upgrade

flutter pub get
flutter pub upgrade --major-versions

Шаг 4: flutter fix

flutter fix --apply

Breaking Changes

Удалённые виджеты:

// ❌ Больше не работает
RaisedButton
FlatButton
OutlineButton

// ✅ Используйте
ElevatedButton
TextButton
OutlinedButton

Тестирование

flutter test
flutter analyze

Заключение

Миграция на Flutter 3.27 в целом безопасна. Следуйте шагам и тестируйте thoroughly.

Рубрики
Flutter

Flutter + Rust: Высокопроизводительные модули

Rust — это системный язык с невероятной производительностью и безопасностью памяти. Комбинируя его с Flutter через FFI (Foreign Function Interface), можно создать приложения с лучшим из обоих миров.

Зачем Rust во Flutter?

  • Производительность — Rust в 10-100x быстрее Dart для вычислений
  • Существующие библиотеки — тысячи crates на crates.io
  • Криптография — безопасные реализации алгоритмов
  • Обработка изображений — быстрое манипулирование пикселями

Установка зависимостей

# pubspec.yaml
dependencies:
  flutter_rust_bridge: ^1.0.0

dev_dependencies:
  ffigen: ^9.0.0

Rust проект

Создайте директорию native/ рядом с lib/:

cargo new --lib native

native/Cargo.toml:

[package]
name = "native"
version = "0.1.0"
edition = "2021"
crate-type = ["cdylib", "staticlib"]

[lib]
name = "native"
path = "lib.rs"

Простая функция

native/src/lib.rs:

use flutter_rust_bridge::frb;

#[frb(sync)] // Синхронная функция
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[frb(init)] // Инициализация
pub fn init_app() {
    // One-time setup
}

native/lib.rs:

pub mod lib;

FFI Bridge

lib/native_bridge.dart:

import 'package:flutter_rust_bridge/flutter_rust_bridge.dart';

part 'native_bridge.g.dart';

@RustCallable()
int add(int a, int b) {
  return addInRust(a, b);
}

@RustCallable(sync: true)
void initApp() {
  initAppInRust();
}

Запуск генерации

cd native
cargo build
cd ..
flutter pub run build_runner build

Использование во Flutter

void main() {
  // Инициализация
  initApp();

  // Вызов Rust функции
  final result = add(10, 20);
  print('Result: $result'); // 30
}

Async операции

native/src/lib.rs:

use flutter_rust_bridge::frb;

#[frb(mirror(PrimeCalculator))]
pub struct PrimeCalculator;

#[frb]
impl PrimeCalculator {
    pub fn new() -> Self {
        Self
    }

    pub async fn calculate_primes_up_to(&self, limit: u64) -> Vec<u64> {
        // Тяжёлая операция
        (2..=limit).filter(|&n| self.is_prime(n)).collect()
    }

    fn is_prime(&self, n: u64) -> bool {
        if n < 2 {
            return false;
        }
        (2..=((n as f64).sqrt() as u64)).all(|i| n % i != 0)
    }
}

lib/primes.dart:

import 'package:flutter_rust_bridge/flutter_rust_bridge.dart';

part 'primes.g.dart';

final calculator = PrimeCalculator();

Future<List<int>> calculatePrimes() async {
  final primes = await calculator.calculatePrimesUpTo(10000);
  return primes.map((e) => e.toInt()).toList();
}

Заключение

Rust + FFI — это инструмент для особых случаев. Используйте его когда критически важна производительность, но помните о сложности интеграции.

Рубрики
Flutter

Drift 2.0: Современная база данных для Flutter

Drift (ранее Moor) — это мощная SQL база данных для Flutter с reactive streams, миграциями и type-safe запросами. Версия 2.0 принесла значительные улучшения.

Что такое Drift?

Drift — это реактивная persistence библиотека для Flutter/Dart.

Преимущества: — Полная поддержка SQL — Типобезопасные запросы — Reactive streams из коробки — Миграции схемы — Web поддержка — Отличная документация

Установка

dependencies:
  drift: ^2.0.0
  sqlite3_flutter_libs: ^2.0.0
  path_provider: ^2.0.0
  path: ^1.8.0

dev_dependencies:
  drift_dev: ^2.0.0
  build_runner: ^2.4.0

Создаём базу данных

import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;

part 'database.g.dart';

class Users extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text()();
  TextColumn get email => text()();
  DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
}

class AppDatabase extends _$AppDatabase {
  AppDatabase(QueryExecutor e) : super(e);
}

// Открываем базу
Future<AppDatabase> openDb() async {
  final dbFolder = await getApplicationDocumentsDirectory();
  final file = File(p.join(dbFolder.path, 'db.sqlite'));
  return AppDatabase(NativeDatabase.createInBackground(file));
}

Запросы

Простые запросы

// Получить всех пользователей
final allUsers = await select(users).get();

// Найти по ID
final user = await (select(users)..where((u) => u.id.equals(1))).getSingle();

// Фильтрация
final activeUsers = await (select(users)
  ..where((u) => u.name.contains('John'))
  ..limit(10)
).get();

Вставка данных

// Вставить одного пользователя
await into(users).insert(UsersCompanion.insert(
  name: 'John',
  email: 'john@example.com',
));

// Вставить несколько
await batch((batch) {
  batch.insertAll(users, [
    UsersCompanion.insert(name: 'Alice', email: 'alice@example.com'),
    UsersCompanion.insert(name: 'Bob', email: 'bob@example.com'),
  ]);
});

Обновление

await (update(users)..where((u) => u.id.equals(1)))
    .write(UsersCompanion(name: Value('Jane')));

Удаление

await (delete(users)..where((u) => u.id.equals(1))).go();

Reactive streams

// Слушать изменения
final userStream = db.select(users).watch();

userStream.listen((users) {
  print('Users updated: ${users.length}');
});

// В виджете
class UserListWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final usersAsync = ref.watch(usersStreamProvider);

    return usersAsync.when(
      data: (users) => ListView.builder(...),
      loading: () => CircularProgressIndicator(),
      error: (err, stack) => Text('Error: $err'),
    );
  }
}

Миграции

// Версия 1
class Users extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text()();
}

// Версия 2
class Users extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text()();
  TextColumn get email => text()(); // Новое поле
}

// Migration
migrationBuilder V1ToV2(Migration m) async {
  await m.addColumn(users, users.email);
}

Заключение

Drift 2.0 — это лучшая SQL база данных для Flutter. Используйте её для локального хранения, кеширования и offline-first приложений.

Рубрики
Flutter

Isolates во Flutter: Многопоточность без головной боли

Dart — это single-threaded язык, но Flutter поддерживает многопоточность через Isolates. В этом руководстве разберём как использовать Isolates для выполнения тяжёлых вычислений без блокировки UI.

Что такое Isolates?

Isolate — это отдельный поток выполнения в Dart с собственной памятью.

Ключевые особенности: — Не делят память между собой — Общаются через сообщения — Избегают race conditions — Полностью изолированы друг от друга

Когда использовать Isolates?

  • Тяжёлые вычисления (парсинг JSON, обработка изображений)
  • Сетевые запросы (хотя лучше использовать http пакет)
  • Криптографические операции
  • База данных операции
  • Любые операции, блокирующие UI более 16ms

Isolate.run() — простой способ

import 'dart:isolate';

// Запуск в isolate
final result = await Isolate.run(() {
  // Тяжёлая операция
  int sum = 0;
  for (int i = 0; i < 1000000000; i++) {
    sum += i;
  }
  return sum;
});

print('Sum: $result'); // Выполнится без блокировки UI

compute из Flutter

import 'package:flutter/foundation.dart';

// Функция для выполнения в isolate
int heavyCalculation(int n) {
  int sum = 0;
  for (int i = 0; i < n; i++) {
    sum += i;
  }
  return sum;
}

// Вызов из UI
Future<int> calculate() async {
  final result = await compute(heavyCalculation, 1000000);
  return result;
}

Передача данных в Isolate

Simple types

// Примитивные типы передаются по значению
final result = await Isolate.run((int value) {
  return value * 2;
}, 5);

Complex types

// Сложные объекты нужно сериализовать
class Message {
  final String text;
  final int value;

  Message(this.text, this.value);

  Map<String, dynamic> toJson() => {
    'text': text,
    'value': value,
  };

  factory Message.fromJson(Map<String, dynamic> json) => Message(
    json['text'] as String,
    json['value'] as int,
  );
}

// Передача через JSON
final result = await Isolate.run((String json) {
  final map = jsonDecode(json) as Map<String, dynamic>;
  final msg = Message.fromJson(map);
  return msg.text.toUpperCase();
}, message.toJson());

ReceivePort и SendPort

import 'dart:isolate';

void isolateMain(SendPort sendPort) {
  // Получаем порт для общения
  final receivePort = ReceivePort();

  // Отправляем родителю свой порт
  sendPort.send(receivePort.sendPort);

  // Слушаем сообщения
  receivePort.listen((message) {
    if (message is String) {
      // Обработка данных
      final result = message.toUpperCase();
      sendPort.send(result);
    } else if (message == 'done') {
      receivePort.close();
    }
  });
}

// Создаём isolate
final receivePort = ReceivePort();
await Isolate.spawn(isolateMain, receivePort.sendPort);

// Получаем порт от isolate
SendPort? sendPort;
await for (final message in receivePort) {
  if (message is SendPort) {
    sendPort = message;
    break;
  }
}

// Отправляем данные
sendPort?.send('hello');

// Получаем результат
await for (final message in receivePort) {
  print('Result: $message');
  if (message == 'DONE') break;
}

Transferable objects

Для больших данных используйте TransferableTypedData:

import 'dart:typed_data';
import 'dart:isolate';

Future<Uint8List> processImage(Uint8List imageBytes) async {
  final receivePort = ReceivePort();

  await Isolate.spawn((SendPort sendPort) {
    final port = ReceivePort();
    sendPort.send(port.sendPort);

    port.listen((message) {
      if (message is TransferableTypedData) {
        // Обработка изображения
        final data = message.materialize();
        // ... обработка ...
        sendPort.send(TransferableTypedData.fromList([1, 2, 3]));
      }
    });
  }, receivePort.sendPort);

  // Отправляем данные
  SendPort? sendPort;
  await for (final msg in receivePort) {
    if (msg is SendPort) {
      sendPort = msg;
      sendPort!.send(TransferableTypedData.fromList(imageBytes));
      break;
    }
  }

  // Получаем результат
  await for (final msg in receivePort) {
    if (msg is TransferableTypedData) {
      receivePort.close();
      return msg.materialize().asUint8List();
    }
  }

  throw 'Failed to process image';
}

Лучшие практики

  1. Избегайте слишком многих isolates — создание isolate дорого
  2. Используйте пул isolates для повторного использования
  3. Закрывайте порты — всегда вызывайте close()
  4. Обрабатывайте ошибки — try/catch в isolate
  5. Профилируйте — убедитесь, что isolate нужен

Заключение

Isolates — мощный инструмент для многопоточности во Flutter. Используйте compute для простых задач и полноценные isolates для сложной работы.

Рубрики
Flutter

Supabase vs Firebase: Что выбрать в 2025 году?

Статья Supabase vs Firebase: Что выбрать в 2025 году? будет добавлена позже.