📀

FlutterでRealmを使ってみた

2023/06/22に公開

Realmに入門してみる

RealmというローカルDBがあるのですが、Swiftで個人アプリを作っていたときに使っていたことがあって、Flutterでも使えるそうなので、試してみました。
https://pub.dev/packages/realm

こちらに公式ドキュメントのリンクも貼っておきます
https://www.mongodb.com/docs/realm/sdk/flutter/install/

動画も作りました

https://youtu.be/MZWXUSgn4R8

やること

  1. パッケージを追加する
flutter pub add realm
  1. モデルクラスを作る。今回はPersonというクラスを作ります。
perons.dart
import 'package:realm/realm.dart';

part 'person.g.dart';// person.g.dartは、RealmObjectを継承するクラスから自動生成されます。

()// @RealmModelは、RealmObjectを継承するクラスに付与するアノテーションです。
class _Person {
  late String name;// lateをつけるのは、RealmObjectはデフォルトコンストラクタを持たないためです。
}
  1. Personというクラスを作ったら、Realmを使うのに、必要なファイルを自動生成します

ファイルを自動生成するコマンド

dart run realm generate

自動生成されたファイル

dart
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'person.dart';

// **************************************************************************
// RealmObjectGenerator
// **************************************************************************

class Person extends _Person with RealmEntity, RealmObjectBase, RealmObject {
  Person(
    String name,
  ) {
    RealmObjectBase.set(this, 'name', name);
  }

  Person._();

  
  String get name => RealmObjectBase.get<String>(this, 'name') as String;
  
  set name(String value) => RealmObjectBase.set(this, 'name', value);

  
  Stream<RealmObjectChanges<Person>> get changes =>
      RealmObjectBase.getChanges<Person>(this);

  
  Person freeze() => RealmObjectBase.freezeObject<Person>(this);

  static SchemaObject get schema => _schema ??= _initSchema();
  static SchemaObject? _schema;
  static SchemaObject _initSchema() {
    RealmObjectBase.registerFactory(Person._);
    return const SchemaObject(ObjectType.realmObject, Person, 'Person', [
      SchemaProperty('name', RealmPropertyType.string),
    ]);
  }
}

Realmの機能を使ってみる

追加と表示の機能は同じページで行っております。

home_page.dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:realm/realm.dart';
import 'package:realm_app/person.dart';

class HomePage extends StatefulWidget {
  
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late Realm realm;// Realmインスタンスを保持するための変数
  final _nameController = TextEditingController();
  final _personsController = StreamController<List<Person>>.broadcast();

  
  void initState() {
    super.initState();
    final config = Configuration.local([Person.schema]);
    realm = Realm(config);
    _fetchPersons();
  }

  
  void dispose() {
    _nameController.dispose();
    _personsController.close();
    super.dispose();
  }

  // _fetchPersonsは、RealmからPersonのリストを取得し、_personsControllerに追加するメソッド
  void _fetchPersons() {
    final persons = realm.all<Person>().toList();
    _personsController.add(persons);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Realm Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: [
            TextField(
              controller: _nameController,
              decoration: InputDecoration(labelText: '名前'),
            ),
            ElevatedButton(
              onPressed: () {
                try {
                  if (_nameController.text.isEmpty) {
                    throw ('名前が入力されてません!');
                  }

                  var person = Person(_nameController.text);
                  realm.write(() {
                    realm.add(person);
                  });
                  _nameController.clear();
                  _fetchPersons();
                } catch (e) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text(e.toString()),
                    ),
                  );
                }
              },
              child: const Text('Add Person'),
            ),
            Expanded(
              child: StreamBuilder<List<Person>>(
                stream: _personsController.stream,
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    return ListView.builder(
                        itemCount: snapshot.data!.length,
                        itemBuilder: (context, index) {
                          final person = snapshot.data![index];
                          return ListTile(
                            title: Text(person.name),
                            trailing: IconButton(
                              icon: const Icon(Icons.delete),
                              onPressed: () {
                                realm.write(() {
                                  realm.delete(person);
                                });
                                _fetchPersons();
                              },
                            ),
                          );
                        });
                  } else if (snapshot.hasError) {
                    return Text('Error: ${snapshot.error}');
                  } else {
                    return const Center(
                      child: Text('データがありません'),
                    );
                  }
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

main.dartでimportしてビルドする

main.dart
import 'package:flutter/material.dart';
import 'package:realm_app/ui/home_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: HomePage(),
    );
  }
}

簡単ですが、メモをする機能を作れました
簡単と言っても設定で苦戦しました笑


まとめ

Swiftで使ったときよりは、すぐにアプリは作れました。これがStoryboardだと2時間ぐらい作るのに時間がかかったような?
Flutterだと作り方を知っていれば、30分ぐらいで作れます。

今回は覚えておくべきことは、モデルを作ってファイルを自動生成して、realmを開く変数を用意して、メソッドを使えるようにすることです。

以下に今回使用した公式のコードの解説を書いておきます

データモデルの作成
アプリケーションのデータモデルを定義するには、アプリケーション・コードに Realm モデル・クラス定義を追加します。

Realm モデルクラスを定義する際に考慮すべき点があります:

クラス定義ファイルの先頭に package をインポートする。

例えば、car.dart ファイルに _Car というクラスを定義します。パブリックな RealmObject クラスを生成するには、次の RealmObject クラスの生成 セクションのコマンドを使用します。このコマンドは、Car のようなパブリック・クラスを出力します。

モデルを定義するコードの前に、生成されたファイル名(part car.g.dart)を含めるようにしてください。これは RealmObject クラスを生成するために必要です。

import 'package:realm/realm.dart';

part 'car.g.dart';

()
class _Car {
  ()
  late ObjectId id;

  late final String make;
  late String? model;
  late int? miles;
}

RealmObject クラスの生成
データモデルのクラス Car から RealmObject クラス Car を生成します:

dart run realm generate

レルムを開く
クラスを使います。
クラスを使用して、 スキーマやレルムをローカル専用にするか同期させるかなど、 オープンするレルムの詳細を制御します。
設定をコンストラクタに渡します:

initStateの中で使ったコードですね。

final config = Configuration.local([Car.schema]);
final realm = Realm(config);

オブジェクトの作成
新しいCarを作成するには、Carクラスのインスタンスを生成し、書き込みトランザクション・ブロックのレルムに追加します:

final car = Car(ObjectId(), 'Tesla', model: 'Model S', miles: 42);
realm.write(() {
  realm.add(car);
});

オブジェクトの削除
車を削除するには
を呼び出して車を削除する。
メソッドを呼び出すことで車を削除します:

realm.write(() {
  realm.delete(car);
});

Discussion