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

nested StreamBuilder bug report (using supabase: ^0.2.8 on Flutter project) #41

Closed
JasonChiu-dev opened this issue Oct 22, 2021 · 9 comments
Labels
bug Something isn't working

Comments

@JasonChiu-dev
Copy link

Bug report

nested StreamBuilder bug report (using supabase: ^0.2.8 on Flutter project)

Describe the bug

A clear and concise description of what the bug is.
I have the same Flutter project running on Firebase with nested StreamBuilders and it works very well and response immediately and correctly when I insert or update data to the Firebase. But when I transfer my Flutter project to Supabase, it works well and I found that Supabase is the good choice for migrating from Firebase to Supabase with RDBS. So I did more tests for my Flutter project on Supabase (which are the same actions running on Firebase), but I found the streams sometimes repose good but sometimes not work.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:
Here are my code snippets:
截圖 2021-10-22 上午11 30 25

  1. Go to '…'
  2. Click on '…'
  3. Scroll down to '…'
  4. See error
    There are two tables in the database and I use nested StreamBuilder to get data from each table.
    When I added the data to 'the first table - owners' the two streams work correctly, but when I added the data to 'the second table - projects' the streams no reaction even the data was really insert to the second table successfully (I checked the Supabase admin console).

here is the error data:
<img width="1231" alt="截圖 2021-10-22 上午10 57 43" src="https://user-images.githubuserco
截圖 2021-10-22 上午10 58 24
ntent.com/81674344/138389231-5949a346-4778-4d5d-8226-529557035395.png">
截圖 2021-10-22 上午10 58 36

Expected behavior

A clear and concise description of what you expected to happen.

Screenshots

If applicable, add screenshots to help explain your problem.
Just like what I get from Firebase with nested StreamBuilders for fetching data from database immediately and correctly.
The stream should response immediately and correctly when I insert or do any action to my database so that I can doing the correct operation for my project and database.

System information

  • OS: macOS Big Sur 11.6
  • Browser chrome
  • version of supabase: supabase: ^0.2.8
  • Flutter 2.5.3 with Dart version 2.14.4
  • Version of supabase-js: N/A
  • Version of Node.js: N/A

截圖 2021-10-22 上午11 30 52

## Additional context

Add any other context about the problem here.

@JasonChiu-dev JasonChiu-dev added the bug Something isn't working label Oct 22, 2021
@JasonChiu-dev
Copy link
Author

resend the images:
<img width="1231" alt="截圖 2021-10-22 上午10 57 43" src="https://user-images.githubusercontent.com/81674344/138390880-748a5b80-5253-4fda-9007-5a0115f69fb9.png"
截圖 2021-10-22 上午10 58 24
截圖 2021-10-22 上午10 58 36

截圖 2021-10-22 上午11 30 25

截圖 2021-10-22 上午11 30 52

@JasonChiu-dev
Copy link
Author

Hi there,

  1. I have turned on realtime like mentioned in this documentation. (https://supabase.io/docs/guides/api#managing-realtime)
  2. Stream doesn’t not work for some tables #29 no information for what I need and it is a no answer issue.
  3. I also report the issue to supabase.io and the I got suggestion to open an issue here.
  4. how to fix this problem??

@dshukertjr
Copy link
Member

Hi @JasonChiu-dev! Thanks for opening an issue here.

Would you mind copy and pasting your code instead of attaching screenshots? That way we can copy and paste it locally to test it right away. If you have a public github repo with your code in it, sharing it here would be extremely helpful!

Aside from the bug that is present on supabase_flutter, I personally avoid using nested StreamBuilders, because every time the parent StreamBuilder rebuilds, the child StreamBuilder goes back to ConnectionState.waiting for a split second, causing the UI to blink. You can follow this example code to avoid nested StreamBuilders. This will not only be a workaround for this issue, but also provide better user experience for your users.

@JasonChiu-dev
Copy link
Author

JasonChiu-dev commented Oct 22, 2021

Yes, I can copy and pasting my code. Here is my sample code for test:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:supabase/supabase.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(title: 'Nested StreamBuilders Test Page'),
    );
  }
}

// Supabase
final _supabase = SupabaseClient('SupabaseURL', 'KEY');

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

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

int _counter = 0;

class _MyHomePageState extends State<MyHomePage> {
  Future<void> _addOwner({required Owner owner}) async {
    try {
      final response = await _supabase.from('owners').insert({
        'ownerId': owner.ownerId,
        'ownerName': owner.ownerName,
      }).execute();

      if (response.error != null) {
        // Error
        print(response.error!.message);
      }
    } catch (e) {
      print('ERROR: ' + e.toString());
    }
  }

  // add a new record(Project) to the projects table.
  Future<void> _addProject({required Project project}) async {
    try {
      final response = await _supabase.from('projects').insert({
        'projectId': project.projectId,
        'projectName': project.projectName,
        'ownerId': project.ownerId,
      }).execute();
      if (response.error != null) {
        // Error
        print(response.error!.message);
      }
    } catch (e) {
      print('ERROR: ' + e.toString());
    }
  }

  Future<void> _incrementCounter() async {
    print('FloatingActionButton onPressed: $_counter');

    if (_counter.isEven) {
      print('addOwner _counter.isEven: $_counter');
      await _addOwner(
        owner: Owner(
          ownerId: _counter.toString(),
          ownerName: _counter.toString() + '-owner',
        ),
      );
    } else {
      print('addProject _counter.isNotEven: $_counter');
      int num = _counter - 1;
      await _addProject(
        project: Project(
          projectId: _counter.toString(),
          projectName: _counter.toString() + '-project',
          ownerId: num.toString(),
        ),
      );
    }
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: const NestedStream(),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: const Icon(Icons.add),
      ),
    );
  }
}

class NestedStream extends StatelessWidget {
  const NestedStream({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // stream-1
    return StreamBuilder<List<Map<String, dynamic>>>(
      stream: _supabase.from('owners').stream().order('ownerName').execute(),
      builder: (context, ownerSnapshot) {
        if (ownerSnapshot.hasError) {
          return Center(
            // To see if it is needed for the error dialog or not
            child: Text(
              ownerSnapshot.error.toString(),
            ),
          );
        }

        print('(Owner) stream: ${ownerSnapshot.connectionState}');
        if (!ownerSnapshot.hasData || ownerSnapshot.connectionState != ConnectionState.active) {
          return const Center(child: CupertinoActivityIndicator());
        }

        print('ownerSnapshot.hasData,  ' + ownerSnapshot.requireData.length.toString() + ' data.');
        List<Owner> owners = _getOwnersFromQuery(ownerSnapshot.requireData);

        // stream-2
        return StreamBuilder<List<Map<String, dynamic>>>(
          stream: _supabase.from('projects').stream().order('projectId').limit(50).execute(),
          builder: (context, projectSnapshot) {
            if (projectSnapshot.hasError) {
              return Center(
                child: Text(projectSnapshot.error.toString()),
              );
            }

            print('(Project) stream: ${projectSnapshot.connectionState}');
            if (!projectSnapshot.hasData || projectSnapshot.connectionState != ConnectionState.active) {
              return const Center(child: CupertinoActivityIndicator());
            }

            print('projectSnapshot.hasData,  ' + projectSnapshot.requireData.length.toString() + ' data.');
            List<Project> projects = _getProjectsFromQuery(projectSnapshot.requireData);

            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(
                    'owners.length = ${owners.length}',
                    style: Theme.of(context).textTheme.headline5,
                  ),
                  const SizedBox(
                    height: 15.0,
                  ),
                  Text(
                    'projects.length = ${projects.length}',
                    style: Theme.of(context).textTheme.headline5,
                  ),
                ],
              ),
            );
          },
        );
      },
    );
  }

  List<Owner> _getOwnersFromQuery(List<Map<String, dynamic>> snapshot) {
    return snapshot.map((doc) {
      return Owner.fromSnapshot(doc);
    }).toList();
  }

  List<Project> _getProjectsFromQuery(List<Map<String, dynamic>> snapshot) {
    return snapshot.map((doc) {
      return Project.fromSnapshot(doc);
    }).toList();
  }
}

class Owner {
  Owner({
    this.id,
    required this.ownerId,
    required this.ownerName,
  });

  // id: generated by default as identity primary key. Automatically assign a sequential unique number to the column.
  int? id;
  String ownerId;
  String ownerName;

  Owner.fromSnapshot(Map<String, dynamic> snapshot)
      : id = snapshot['id'], // Automatically assign a sequential unique number to the column and is primary key
        ownerId = snapshot['ownerId'],
        ownerName = snapshot['ownerName'];
}

class Project {
  Project({
    this.id,
    required this.projectId,
    required this.projectName,
    required this.ownerId,
  });

  // id: generated by default as identity primary key. Automatically assign a sequential unique number to the column.
  int? id;
  String projectId;
  String projectName;
  String ownerId; // foreign key reference to TABLE owners.ownerId

  Project.fromSnapshot(Map<String, dynamic> snapshot)
      : id = snapshot['id'],
        projectId = snapshot['projectId'],
        projectName = snapshot['projectName'],
        ownerId = snapshot['ownerId'];
}

For your information.

Jason

@dshukertjr
Copy link
Member

Thanks @JasonChiu-dev!

Did you have any chances to try out my workaround?

@JasonChiu-dev
Copy link
Author

Yes, I just try out the workaround you mentioned above and it works. Thanks for your help.
By the way, the nested StreamBuilders usage is workable on Flutter and Firebase. Is there any way to fix Supabase and make it easier for migrating to Supabase (from Firebase)?

@dshukertjr
Copy link
Member

@JasonChiu-dev
Glad to hear that the work around works!

Yes, I'm sure there is a bug causing supabase_dart to not work with nested StreamBuilders (not sure what the cause is. We would love to fix the bug at some point, but since nested StreamBuilders are generally not a very good practice even with Firebase because of the blinking effect, it is not the highest priority.

If you could try to pinpoint the cause, that will be great!

@dshukertjr
Copy link
Member

I will close this issue in favor of the issue here.

@JasonChiu-dev
Copy link
Author

OK. Thank you so much for your help.

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

No branches or pull requests

2 participants