Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Different behavior when listening to streams (from drift) using StreamProvider vs StreamBuilder #3832

Open
AhmedLSayed9 opened this issue Nov 13, 2024 · 3 comments
Assignees
Labels
bug Something isn't working help wanted Extra attention is needed

Comments

@AhmedLSayed9
Copy link
Contributor

Note: I couldn't re-produce the issue using raw streams, so you need to add the following dependencies in addition to Riverpod:

dependencies:
  drift: ^2.21.0
  drift_flutter: ^0.1.0
dev_dependencies:
  build_runner:
  drift_dev: ^2.21.0

The example is very simply:

import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:flutter/material.dart' hide Table;
import 'package:hooks_riverpod/hooks_riverpod.dart';

part 'main.g.dart';

class TodoItems extends Table {
  IntColumn get id => integer().autoIncrement()();
}

@DriftDatabase(tables: [TodoItems])
class AppDatabase extends _$AppDatabase {
  AppDatabase() : super(_openConnection());

  @override
  int get schemaVersion => 1;

  static QueryExecutor _openConnection() => driftDatabase(name: 'my_database');
}

final database = AppDatabase();

final todoStreamProvider = StreamProvider.autoDispose<List<int>>((ref) {
  return todoStream();
});

Stream<List<int>> todoStream() {
  return database.managers.todoItems.watch().map((value) {
    print('map Called with $value');
    return [];
  });
}

void main() async {
  runApp(const ProviderScope(child: MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

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

  @override
  HomePageState createState() => HomePageState();
}

class HomePageState extends State<HomePage> {
  // TODO: uncomment this and use StreamBuilder instead of stream provider.
  //final stream = todoStream();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('CombineLatest2 Example')),
      body: Center(
        child: Consumer(
          builder: (context, ref, child) {
            ref.watch(todoStreamProvider);
            return const SizedBox();
          },
        ),
        /* child: StreamBuilder(
          stream: stream,
          builder: (context, snapshot) {
            return const SizedBox();
          },
        ), */
      ),
    );
  }
}

Steps:

  1. Run the example as is (after running build runner for the generated code).
  2. Notice the print statement print('map Called with $value'); prints twice.
  3. Uncomment the line final stream = todoStream(); and the StreamBuilder code, and comment the Consumer code.
  4. Re-run the example.
  5. Notice the print statement print('map Called with $value'); prints once.
@rrousselGit
Copy link
Owner

I don't the time to investigate third-party packages. If this is important for you, I'd suggest looking into the issue yourself :)

@rrousselGit rrousselGit added help wanted Extra attention is needed and removed needs triage labels Nov 13, 2024
@AhmedLSayed9
Copy link
Contributor Author

I don't the time to investigate third-party packages. If this is important for you, I'd suggest looking into the issue yourself :)

I'm not sure if it's related to Riverpod or Drift (or not an issue after all), but I've leaned towards Riverpod as the same stream works fine with StreamBuilder. I'll try to dig into this more anyway.

@AhmedLSayed9
Copy link
Contributor Author

Note to myself:
Manually listening to the stream:

final todoStreamProvider = StreamProvider.autoDispose<List<int>>((ref) {
  final controller = StreamController<List<int>>();
  todoStream().listen((v) => controller.add(v));
  ref.onDispose(() => controller.close());
  return controller.stream;
});

instead of returning the stream directly and let riverpod internally handle the listening:

final todoStreamProvider = StreamProvider.autoDispose<List<int>>((ref) {
  return todoStream();
});

works as expected similar to StreamBuilder

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants