flutterdartflutter-isar

Flutter Isar stops working after hot restart


I started learning flutter isar yesterday and I couldn't love it more. I created a demo app with it and for some reason, it is not working as expected.

The app has two sections: Original(This contain the dummyData) and the Database(this contains data in the isar database).

When an item is starred in the original, it is added in the database and the icon is changed to filled_star. When the item is unstarred in the original section, it is expected to be removed from the database section and the icon is expected to change to star_outline. This is works fine.

However, when the app is hot-restarted, I am unable to unstar the items. Check the GIF below.

enter image description here

main.dart

import 'package:flutter/material.dart';
import 'package:isardemo/isar_files/course.dart';
import 'package:isardemo/isar_files/isar.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Flutter Demo',
        theme: ThemeData(
          brightness: Brightness.dark,
          primarySwatch: Colors.blueGrey,
        ),
        home: const MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final dummyData = [
    Course()
      ..title = 'MTH 212'
      ..courseId = '1ab',
    Course()
      ..title = 'STS 432'
      ..courseId = '2bc',
    Course()
      ..title = 'SHS 555'
      ..courseId = '3de',
    Course()
      ..title = 'HMM 999'
      ..courseId = '4fg',
    Course()
      ..title = 'EEE 666'
      ..courseId = '5hi',
  ];
  Future<void> onFavTap(IsarService courseData, Course course) async {
    if (await courseData.isItemDuplicate(course)) {
      await courseData.deleteCourse(course);
      setState(() {});
      debugPrint('${course.courseId} deleted');
    } else {
      await courseData.addCourse(course);
      setState(() {});
      debugPrint('${course.courseId} added');
    }
  }

  final courseData = IsarService();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Isar'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(20),
        children: [
          Center(
            child: FutureBuilder(
              initialData: courseData,
              future: courseData.favoritesCount(),
              builder: (BuildContext context, AsyncSnapshot snapshot) {
                if (snapshot.hasData) {
                  return Text(
                    snapshot.data.toString(),
                    style:
                        const TextStyle(fontSize: 25, color: Colors.lightGreen),
                  );
                } else {
                  return const LinearProgressIndicator();
                }
              },
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: ElevatedButton(
                onPressed: () {
                  courseData.cleanDb();
                  setState(() {});
                },
                child: const Text('Destroy Database')),
          ),
          const Text('Original',
              style: TextStyle(fontSize: 30, color: Colors.green)),
          ListView.separated(
            shrinkWrap: true,
            separatorBuilder: (context, index) => const SizedBox(height: 5),
            itemCount: dummyData.length,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(
                tileColor: Colors.blueGrey,
                title: Text(dummyData[index].title),
                trailing: IconButton(
                    icon: FutureBuilder(
                      // initialData: courseData.isItemDuplicate(dummyData[index]),
                      future: courseData.isItemDuplicate(dummyData[index]),
                      builder: (BuildContext context, AsyncSnapshot snapshot) {
                        if (snapshot.connectionState == ConnectionState.done) {
                          if (snapshot.data) {
                            return const Icon(Icons.star);
                          } else {
                            return const Icon(Icons.star_border_outlined);
                          }
                        }
                        return const Icon(Icons.g_mobiledata,
                            color: Colors.green);
                      },
                    ),
                    onPressed: () => onFavTap(courseData, dummyData[index])),
              );
            },
          ),
          const SizedBox(height: 20),
          const Text(
            'database',
            style: TextStyle(fontSize: 30, color: Colors.green),
          ),
          FutureBuilder(
              future: courseData.getAllCourses(),
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return ListView.builder(
                    shrinkWrap: true,
                    itemCount: snapshot.data!.length,
                    itemBuilder: (BuildContext context, int index) {
                      return ListTile(
                        title: Text(snapshot.data![index].title),
                        trailing: InkWell(
                            onTap: () async {
                              await courseData
                                  .deleteCourse(snapshot.data![index]);
                              setState(() {});
                            },
                            child: const Icon(Icons.star)),
                      );
                    },
                  );
                } else {
                  return const Center(
                    child: LinearProgressIndicator(),
                  );
                }
              }),
        ],
      ),
    );
  }
}

course.dart

import 'package:isar/isar.dart';

part 'course.g.dart';

@Collection()
class Course {
  Id id = Isar.autoIncrement;

  late String courseId;

  late String title;

  late bool isFavorite = false; // new property
}

isar.dart

import 'package:path_provider/path_provider.dart';
import 'course.dart';

class IsarService {
  late Future<Isar> _db;

  IsarService() {
    _db = openIsar();
  }

  Future<Isar> openIsar() async {
    if (Isar.instanceNames.isEmpty) {
      final directory = await getApplicationDocumentsDirectory();
      return await Isar.open([CourseSchema],
          inspector: true, directory: directory.path);
    } else {
      return await Future.value(Isar.getInstance());
    }
  }

  Future<void> addCourse(Course course) async {
    final isar = await _db;

    await isar.writeTxn(() async {
      await isar.courses.put(course);
    });
  }

  Future<bool> isItemDuplicate(Course course) async {
    final isar = await _db;
    final count =
        await isar.courses.filter().courseIdContains(course.courseId).count();
    return count > 0;
  }

  Future<List<Course>> getAllCourses() async {
    final isar = await _db;
    return isar.courses.where().findAll();
  }

  Future<void> deleteCourse(Course course) async {
    final isar = await _db;

    await isar.writeTxn(() async {
      await isar.courses.delete(course.id);
    });
  }

  Future<String> favoritesCount() async {
    final isar = await _db;
    final count = await isar.courses.count();
    return count.toString();
  }

  Future<void> cleanDb() async {
    final isar = await _db;
    await isar.writeTxn(() => isar.clear());
  }
}

I tried downgrading the isar version but it didn't work.


Solution

  • I fixed it! Instead of auto-incrementing the id, I used the Course's id instead. But Id expects an integer so I had to convert the Course's id into an integer using the fastHash.

    Learn more

    course.dart

    @Collection()
    class Course {
      late String id;
    
      Id get courseId => fastHash(id);
    
      late String title;
    
    }
    
    int fastHash(String string) {
      var hash = 0xcbf29ce484222325;
    
      var i = 0;
      while (i < string.length) {
        final codeUnit = string.codeUnitAt(i++);
        hash ^= codeUnit >> 8;
        hash *= 0x100000001b3;
        hash ^= codeUnit & 0xFF;
        hash *= 0x100000001b3;
      }
    
      return hash;
    }