Isar Plus

Watcher'lar

Veritabanı değişikliklerine gerçek zamanlı tepki verin

Watcher'lar

Watcher'lar veritabanınızdaki değişikliklere abone olmanıza ve verimli şekilde tepki vermenize olanak tanır. Gerçek zamanlı UI güncellemeleri ve senkronizasyon işlemleri için idealdir.

Watcher'lar yalnızca bir transaction başarıyla commit edildikten ve hedef gerçekten değiştiğinde tetiklenir.

Genel Bakış

Şunları izleyebilirsiniz:

  • Belirli nesneler - Tek bir nesne değiştiğinde bildirim alın
  • Koleksiyonlar - Koleksiyondaki herhangi bir nesne değiştiğinde bildirim alın
  • Sorgular - Sorgu sonuçları değiştiğinde bildirim alın

Nesne İzleme

ID'sine göre belirli bir nesneyi izleyin:

@collection
class User {
  Id? id;
  late String name;
  late int age;
}
// Güncellenmiş nesneyi al
Stream<User?> userStream = isar.users.watchObject(5);

userStream.listen((user) {
  if (user == null) {
    print('User deleted');
  } else {
    print('User changed: ${user.name}');
  }
});

// Değişiklik tetikle
final user = User()..id = 5..name = 'David'..age = 25;
await isar.writeAsync((isar) => isar.users.put(user));
// Çıktı: User changed: David

user.name = 'Mark';
await isar.writeAsync((isar) => isar.users.put(user));
// Çıktı: User changed: Mark

await isar.writeAsync((isar) => isar.users.delete(5));
// Çıktı: User deleted
// Sadece bildir, nesneyi alma
Stream<void> userStream = isar.users.watchObjectLazy(5);

userStream.listen((_) {
  print('User 5 changed');
});

final user = User()..id = 5..name = 'David'..age = 25;
await isar.writeAsync((isar) => isar.users.put(user));
// Çıktı: User 5 changed

Nesnenin henüz var olması gerekmez. Oluşturulduğunda watcher sizi bilgilendirir.

Hemen Tetikle

Mevcut değeri anında alın:

Stream<User?> userStream = isar.users.watchObject(
  5,
  fireImmediately: true,
);

userStream.listen((user) {
  print('User: ${user?.name}');
});
// Mevcut değeri (veya null) hemen yazar

Koleksiyon İzleme

Bir koleksiyondaki tüm değişiklikleri izleyin:

// Sadece bildirim al
Stream<void> usersStream = isar.users.watchLazy();

usersStream.listen((_) {
  print('A user changed');
});

await isar.writeAsync((isar) async {
  isar.users.put(User()..name = 'Alice');
});
// Çıktı: A user changed
// Her değişiklikte tüm nesneleri getir
Stream<List<User>> usersStream = isar.users.watch();

usersStream.listen((users) {
  print('Users: ${users.map((u) => u.name).join(', ')}');
});

await isar.writeAsync((isar) async {
  isar.users.put(User()..name = 'Alice');
});
// Çıktı: Users: Alice

Tam nesnelerle koleksiyon izlemek büyük koleksiyonlarda pahalı olabilir!

Sorgu İzleme

Belirli bir sorgunun sonuçlarını izleyin:

@collection
class User {
  Id? id;
  late String name;
  late int age;
}
// Sorgu oluştur
final adultsQuery = isar.users
  .where()
  .ageGreaterThan(18)
  .build();

// Sorgu sonuçlarını izle
Stream<List<User>> adultsStream = adultsQuery.watch(
  fireImmediately: true,
);

adultsStream.listen((adults) {
  print('Adults: ${adults.map((u) => u.name).join(', ')}');
});
// Mevcut sonuçları hemen yazar

// Çocuk ekle (bildirim yok)
await isar.writeAsync((isar) async {
  isar.users.put(User()..name = 'Child'..age = 10);
});
// Çıkış yok - sorguya uymuyor

// Yetişkin ekle (bildirim gelir)
await isar.writeAsync((isar) async {
  isar.users.put(User()..name = 'Alice'..age = 25);
});
// Çıktı: Adults: Alice

// Başka yetişkin
await isar.writeAsync((isar) async {
  isar.users.put(User()..name = 'Bob'..age = 30);
});
// Çıktı: Adults: Alice, Bob

Sorgu watcher'ları yalnızca sonuçlar gerçekten değiştiğinde tetiklenir!

Lazy Sorgu İzleme

final adultsQuery = isar.users
  .where()
  .ageGreaterThan(18)
  .build();

Stream<void> adultsStream = adultsQuery.watchLazy();

adultsStream.listen((_) {
  print('Adult users changed');
});

Sorgu Watcher Sınırlamaları

offset, limit veya distinct kullanırken, görünür sonuçlar değişmemiş olsa bile bildirim gelebilir.

// Fazladan bildirim olabilir
final topUsers = isar.users
  .where()
  .sortByAge()
  .limit(10)
  .build();

topUsers.watch().listen((users) {
  // İlk 10 değişmemiş olsa da tetiklenebilir
});

Gerçek Dünya Örnekleri

Flutter UI Güncellemeleri

class UserListWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<User>>(
      stream: isar.users.where().watch(fireImmediately: true),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return CircularProgressIndicator();
        }
        
        final users = snapshot.data!;
        return ListView.builder(
          itemCount: users.length,
          itemBuilder: (context, index) {
            final user = users[index];
            return ListTile(
              title: Text(user.name),
              subtitle: Text('Age: ${user.age}'),
            );
          },
        );
      },
    );
  }
}

Kullanıcı Profili

class UserProfileWidget extends StatelessWidget {
  final int userId;
  
  const UserProfileWidget({required this.userId});
  
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: isar.users.watchObject(userId, fireImmediately: true),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return Text('User not found');
        }
        
        final user = snapshot.data!;
        return Column(
          children: [
            Text('Name: ${user.name}'),
            Text('Age: ${user.age}'),
          ],
        );
      },
    );
  }
}

Arama Sonuçları

class SearchWidget extends StatefulWidget {
  @override
  _SearchWidgetState createState() => _SearchWidgetState();
}

class _SearchWidgetState extends State<SearchWidget> {
  String searchQuery = '';
  
  @override
  Widget build(BuildContext context) {
    final query = isar.users
      .where()
      .nameContains(searchQuery, caseSensitive: false)
      .build();
    
    return Column(
      children: [
        TextField(
          onChanged: (value) {
            setState(() {
              searchQuery = value;
            });
          },
        ),
        Expanded(
          child: StreamBuilder<List<User>>(
            stream: query.watch(fireImmediately: true),
            builder: (context, snapshot) {
              if (!snapshot.hasData) return SizedBox();
              
              final users = snapshot.data!;
              return ListView.builder(
                itemCount: users.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(users[index].name),
                  );
                },
              );
            },
          ),
        ),
      ],
    );
  }
}

Sunucuya Senkronizasyon

class SyncService {
  void startWatching() {
    isar.users.watchLazy().listen((_) async {
      await syncUsersToServer();
    });
  }
  
  Future<void> syncUsersToServer() async {
    final users = await isar.users.where().findAll();
    // Sunucuya gönder...
  }
}

Cache Geçersiz Kılma

class CacheService {
  final _cache = <int, User>{};
  
  void startWatching() {
    isar.users.watchLazy().listen((_) {
      _cache.clear();
      print('Cache invalidated');
    });
  }
  
  Future<User?> getUser(int id) async {
    if (_cache.containsKey(id)) {
      return _cache[id];
    }
    
    final user = await isar.users.get(id);
    if (user != null) {
      _cache[id] = user;
    }
    return user;
  }
}

Performans Dikkatleri

// ✅ Veriye ihtiyacınız yoksa lazy watcher kullanın
isar.users.watchLazy().listen((_) {
  // Sadece cache invalid et
  needsRefresh = true;
});

// ✅ Tüm koleksiyon yerine spesifik sorguları izleyin
isar.users
  .where()
  .statusEqualTo('active')
  .watch()
  .listen((activeUsers) {
    // Aktif kullanıcıları işle
  });

// ✅ İşiniz bitince abonelikleri iptal edin
final subscription = isar.users.watchLazy().listen((_) {});
// Daha sonra...
subscription.cancel();
// ❌ Büyük koleksiyonları tam veriyle izlemeyin
isar.users.watch().listen((allUsers) {
  // Her değişiklikte TÜM kullanıcıları yeniden çeker
});

// ❌ Listener içinde ağır işlemler yapmayın
isar.users.watchLazy().listen((_) async {
  // Ağır işlem
  await processAllUsers();
});

// ❌ Abonelikleri iptal etmeyi unutmayın
isar.users.watchLazy().listen((_) {
  // İptal etmezseniz sonsuza kadar çalışır
});

StreamBuilder ile Kullanım

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<User>>(
      stream: isar.users
        .where()
        .ageGreaterThan(18)
        .build()
        .watch(fireImmediately: true),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return CircularProgressIndicator();
        }
        
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        
        if (!snapshot.hasData || snapshot.data!.isEmpty) {
          return Text('No users found');
        }
        
        final users = snapshot.data!;
        return ListView(
          children: users.map((user) => 
            ListTile(title: Text(user.name))
          ).toList(),
        );
      },
    );
  }
}

En İyi Uygulamalar

  1. Sadece bildirim gerekiyorsa lazy watcher kullanın
  2. Tüm koleksiyon yerine spesifik sorguları izleyin
  3. Widget dispose olduğunda abonelikleri iptal edin
  4. Watcher callback'lerinde ağır işlemlerden kaçının
  5. İlk UI durumu için fireImmediately kullanın

Watcher'lar verimli ve hafiftir. Reaktif UI'lar oluşturmak için özgürce kullanın!

Sonraki Adımlar

Son Güncelleme