firebase / flutterfire

🔥 A collection of Firebase plugins for Flutter apps.
https://firebase.google.com/docs/flutter/setup
BSD 3-Clause "New" or "Revised" License
8.71k stars 3.97k forks source link

🐛 Firebase_Database Assertion error on push for sorted FirebaseAnimatedList #7100

Closed johnwargo closed 1 year ago

johnwargo commented 3 years ago

Bug report

Describe the bug I build a Flutter application using the firebase database plugin against the Firebase Realtime Database. I'm using the FirebaseAnimatedList to render the documents from a collection and when I add a sort to the FirebaseAnimatedList, I get an assertion error whenever I add a new document to the collection using _recordsRef.push().set(record.toJson());

Here's the error I get from Crashalytics:

----------------FIREBASE CRASHLYTICS----------------
I/flutter (15125): 'package:flutter/src/widgets/animated_list.dart': Failed assertion: line 959 pos 12: 'index != null && index >= 0': is not true.
I/flutter (15125): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39)
I/flutter (15125): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
I/flutter (15125): #2      SliverAnimatedListState.insertItem (package:flutter/src/widgets/animated_list.dart:959:12)
I/flutter (15125): #3      AnimatedListState.insertItem (package:flutter/src/widgets/animated_list.dart:484:42)
I/flutter (15125): #4      FirebaseAnimatedListState._onChildAdded (package:firebase_database/ui/firebase_animated_list.dart:175:36)
I/flutter (15125): #5      FirebaseSortedList._onChildAdded (package:firebase_database/ui/firebase_sorted_list.dart:97:18)
I/flutter (15125): #6      _rootRunUnary (dart:async/zone.dart:1436:47)
I/flutter (15125): #7      _CustomZone.runUnary (dart:async/zone.dart:1335:19)
I/flutter (15125): #8      _CustomZone.runUnaryGuarded (dart:async/zone.dart:1244:7)
I/flutter (15125): #9      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:341:11)
I/flutter (15125): #10     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:271:7)
I/flutter (15125): #11     _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:123:11)
I/flutter (15125): #12     _MapStream._handleData (dart:a
I/flutter (15125): ----------------------------------------------------

When I comment out the sort everything works as expected

Steps to reproduce

Create an app with the plugin. Create a document list using:

Widget recordList() {
    final DateFormat formatter = DateFormat('yyyy-MM-dd @ hh:mm:ss');

    return FirebaseAnimatedList(
      controller: _scrollController,
      query: getRecordsQuery(),
      sort: (a, b) {
        return a.value['timestamp'].compareTo(b.value['timestamp']);
      },
      itemBuilder: (context, snapshot, animation, index) {
        final json = snapshot.value as Map<dynamic, dynamic>;
        final record = Record.fromJson(json);
        var date = DateTime.fromMicrosecondsSinceEpoch(record.timestamp * 1000);
        return ListTile(
          title: Text("Message: ${record.msg}"),
          subtitle: Text("${record.user} (${formatter.format(date)}))"),
        );
      },
    );
  }

add a document to the collection using:

_recordsRef.push().set(record.toJson());

my record looks like this:

class Record {
  final String msg;
  final String user;
  final int timestamp;

  Record(this.msg, this.user, this.timestamp);

  Record.fromJson(Map<dynamic, dynamic> json)
      : msg = json['msg'],
        user = json['user'],
        timestamp = json['timestamp'];

  Map<dynamic, dynamic> toJson() =>
      {'msg': msg, 'user': user, 'timestamp': timestamp};
}

Expected behavior

Its an assertion error, so I assume something's going on inside the package. I expect there to be no assertion errors when I insert a document into the collection while using an FirebaseAnimatedList with a sort method.

Sample project

Providing a minimal example project which demonstrates the bug in isolation from your main App greatly enhances the chance of a timely fix. Please link to the public repository URL.


Additional context

Add any other context about the problem here.


Flutter doctor

Run flutter doctor and paste the output below:

Click To Expand ``` Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 2.5.1, on macOS 11.6 20G165 darwin-x64, locale en-US) [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0) [✓] Xcode - develop for iOS and macOS [✓] Chrome - develop for the web [✓] Android Studio (version 2020.3) [✓] VS Code (version 1.60.2) [✓] Connected device (2 available) • No issues found! ```

Flutter dependencies

Run flutter pub deps -- --style=compact and paste the output below:

Click To Expand ``` Dart SDK 2.14.2 Flutter SDK 2.5.1 fb_demo_flutter 1.0.0+1 dependencies: - firebase_analytics 8.3.2 [firebase_analytics_platform_interface firebase_analytics_web firebase_core flutter meta] - firebase_auth 3.1.1 [firebase_auth_platform_interface firebase_auth_web firebase_core firebase_core_platform_interface flutter meta] - firebase_core 1.6.0 [firebase_core_platform_interface firebase_core_web flutter meta] - firebase_crashlytics 2.2.1 [firebase_core firebase_core_platform_interface firebase_crashlytics_platform_interface flutter stack_trace] - firebase_database 8.0.0 [firebase_core firebase_database_platform_interface firebase_database_web flutter] - flutter 0.0.0 [characters collection meta typed_data vector_math sky_engine] - fluttertoast 8.0.8 [flutter flutter_web_plugins] - google_sign_in 5.1.0 [flutter google_sign_in_platform_interface google_sign_in_web meta] - package_info 2.0.2 [flutter] - random_string 2.3.1 - shared_preferences 2.0.8 [flutter meta shared_preferences_linux shared_preferences_macos shared_preferences_platform_interface shared_preferences_web shared_preferences_windows] - url_launcher 6.0.11 [flutter meta url_launcher_linux url_launcher_macos url_launcher_platform_interface url_launcher_web url_launcher_windows] dev dependencies: - flutter_lints 1.0.4 [lints] - flutter_test 0.0.0 [flutter test_api path fake_async clock stack_trace vector_math async boolean_selector characters charcode collection matcher meta source_span stream_channel string_scanner term_glyph typed_data] transitive dependencies: - async 2.8.1 [collection meta] - boolean_selector 2.1.0 [source_span string_scanner] - characters 1.1.0 - charcode 1.3.1 - clock 1.1.0 - collection 1.15.0 - fake_async 1.2.0 [clock collection] - ffi 1.1.2 - file 6.1.2 [meta path] - firebase 9.0.2 [http http_parser js] - firebase_analytics_platform_interface 2.0.1 [flutter meta] - firebase_analytics_web 0.3.0+1 [firebase firebase_analytics_platform_interface flutter flutter_web_plugins meta] - firebase_auth_platform_interface 6.1.0 [firebase_core flutter meta plugin_platform_interface] - firebase_auth_web 3.1.0 [firebase_auth_platform_interface firebase_core firebase_core_web flutter flutter_web_plugins http_parser intl js meta] - firebase_core_platform_interface 4.0.1 [collection flutter meta plugin_platform_interface] - firebase_core_web 1.1.0 [firebase_core_platform_interface flutter flutter_web_plugins js meta] - firebase_crashlytics_platform_interface 3.1.2 [collection firebase_core flutter meta plugin_platform_interface] - firebase_database_platform_interface 0.1.0 [collection firebase_core flutter meta plugin_platform_interface] - firebase_database_web 0.1.0 [firebase_core firebase_core_web firebase_database_platform_interface flutter flutter_web_plugins js] - flutter_web_plugins 0.0.0 [flutter js characters collection meta typed_data vector_math] - google_sign_in_platform_interface 2.0.1 [flutter meta quiver] - google_sign_in_web 0.10.0+3 [flutter flutter_web_plugins google_sign_in_platform_interface js meta] - http 0.13.3 [async http_parser meta path pedantic] - http_parser 4.0.0 [charcode collection source_span string_scanner typed_data] - intl 0.17.0 [clock path] - js 0.6.3 - lints 1.0.1 - matcher 0.12.10 [stack_trace] - meta 1.7.0 - path 1.8.0 - path_provider_linux 2.1.0 [flutter path path_provider_platform_interface xdg_directories] - path_provider_platform_interface 2.0.1 [flutter meta platform plugin_platform_interface] - path_provider_windows 2.0.3 [ffi flutter meta path path_provider_platform_interface win32] - pedantic 1.11.1 - platform 3.0.2 - plugin_platform_interface 2.0.1 [meta] - process 4.2.3 [file path platform] - quiver 3.0.1 [matcher] - shared_preferences_linux 2.0.2 [file meta flutter path path_provider_linux shared_preferences_platform_interface] - shared_preferences_macos 2.0.2 [flutter shared_preferences_platform_interface] - shared_preferences_platform_interface 2.0.0 [flutter] - shared_preferences_web 2.0.2 [flutter flutter_web_plugins meta shared_preferences_platform_interface] - shared_preferences_windows 2.0.2 [flutter file meta path path_provider_platform_interface path_provider_windows shared_preferences_platform_interface] - sky_engine 0.0.99 - source_span 1.8.1 [collection path term_glyph] - stack_trace 1.10.0 [path] - stream_channel 2.1.0 [async] - string_scanner 1.1.0 [charcode source_span] - term_glyph 1.2.0 - test_api 0.4.2 [async boolean_selector collection meta source_span stack_trace stream_channel string_scanner term_glyph matcher] - typed_data 1.3.0 [collection] - url_launcher_linux 2.0.2 [flutter] - url_launcher_macos 2.0.2 [flutter] - url_launcher_platform_interface 2.0.4 [flutter plugin_platform_interface] - url_launcher_web 2.0.4 [flutter flutter_web_plugins meta url_launcher_platform_interface] - url_launcher_windows 2.0.2 [flutter] - vector_math 2.1.0 - win32 2.2.9 [ffi] - xdg_directories 0.2.0 [meta path process] ```

markusaksli-nc commented 3 years ago

Hi @johnwargo Could you please provide a minimal complete reproducible code sample? Thank you

johnwargo commented 3 years ago

@markusaksli-nc here ya go:

my main.dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_analytics/observer.dart';

import 'pages/home.dart';

// Toggle this to cause an async error to be thrown during initialization
// and to test that runZonedGuarded() catches the error
const _kShouldTestAsyncErrorOnInit = false;

// Toggle this for testing Crashlytics in your app locally.
const _kTestingCrashlytics = true;

const appName = 'Flutter Firebase Demo';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  // runApp(const MyApp());
  runZonedGuarded(() {
    runApp(MyApp());
  }, FirebaseCrashlytics.instance.recordError);
}

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

  static FirebaseAnalytics analytics = FirebaseAnalytics();
  static FirebaseAnalyticsObserver observer =
      FirebaseAnalyticsObserver(analytics: analytics);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Future<void> _initializeFlutterFireFuture;

  Future<void> _testAsyncErrorOnInit() async {
    Future<void>.delayed(const Duration(seconds: 2), () {
      final List<int> list = <int>[];
      print(list[100]);
    });
  }

  // Define an async function to initialize FlutterFire
  Future<void> _initializeFlutterFire() async {
    // Wait for Firebase to initialize

    if (_kTestingCrashlytics) {
      // Force enable crashlytics collection enabled if we're testing it.
      await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
    } else {
      // Else only enable it in non-debug builds.
      // You could additionally extend this to allow users to opt-in.
      await FirebaseCrashlytics.instance
          .setCrashlyticsCollectionEnabled(!kDebugMode);
    }

    // Pass all uncaught errors to Crashlytics.
    Function originalOnError = FlutterError.onError as Function;

    FlutterError.onError = (FlutterErrorDetails errorDetails) async {
      await FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
      // Forward to original handler.
      originalOnError(errorDetails);
    };

    if (_kShouldTestAsyncErrorOnInit) {
      await _testAsyncErrorOnInit();
    }
  }

  @override
  void initState() {
    super.initState();
    _initializeFlutterFireFuture = _initializeFlutterFire();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: appName,
      debugShowCheckedModeBanner: false,
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      home: FirebaseDemoHome(
        title: appName,
        analytics: MyApp.analytics,
        observer: MyApp.observer,
      ),
      navigatorObservers: [
        FirebaseAnalyticsObserver(analytics: MyApp.analytics),
      ],
    );
  }
}

and home.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:firebase_database/ui/firebase_animated_list.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:intl/intl.dart';
import 'package:random_string/random_string.dart';

final GoogleSignIn _googleSignIn = GoogleSignIn();
late Future initFuture;

class FirebaseDemoHome extends StatefulWidget {
  const FirebaseDemoHome({
    Key? key,
    required this.title,
    required this.analytics,
    required this.observer,
  }) : super(key: key);

  final String title;
  final FirebaseAnalytics analytics;
  final FirebaseAnalyticsObserver observer;

  @override
  State<FirebaseDemoHome> createState() => _FirebaseDemoHomeState();
}

class _FirebaseDemoHomeState extends State<FirebaseDemoHome> {
  late DatabaseReference _recordsRef;
  late StreamSubscription<Event> _recordsSubscription;
  late bool isLoggedIn;

  final ScrollController _scrollController = ScrollController();

  Future<UserCredential> _signInWithGoogle() async {
    // move this into the login method
    print("Signing-in with Google");
    await widget.analytics.logEvent(name: "login");
    // Trigger the authentication flow
    final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
    print("signed in");
    // Obtain the auth details from the request
    final GoogleSignInAuthentication googleAuth =
        await googleUser!.authentication;
    print("got auth details");
    // Create a new credential
    final credential = GoogleAuthProvider.credential(
      accessToken: googleAuth.accessToken,
      idToken: googleAuth.idToken,
    );
    print("got credentials, executing Firebase sign-in");
    // Once signed in, return the UserCredential
    return await FirebaseAuth.instance.signInWithCredential(credential);
  }

  _doLogin() {
    print('Home: Login button tapped');
    _signInWithGoogle().then((UserCredential theUser) {
      print("UID: ${theUser.user!.uid}");
      setState(() {
        isLoggedIn = theUser.user!.uid.isNotEmpty;
      });
    });
  }

  _doLogout() {
    // logs out of Firebase and Google
    print('Home: Logout button tapped');
    FirebaseAuth.instance.signOut().then((value) {
      _googleSignIn.disconnect();
      setState(() {
        isLoggedIn = (FirebaseAuth.instance.currentUser != null);
      });
    }).catchError((error) {
      print("Unable to logout");
      print(error);
    });
  }

  // https://www.raywenderlich.com/24346128-firebase-realtime-database-tutorial-for-flutter
  Query getRecordsQuery() {
    return _recordsRef;
  }

  // Initialize the Config class (loading data) for the FutureBuilder
  Future<bool> initializeApp() async {
    print('HomePage: initializeApp()');
    // Initialize the Records database (Firebase)
    _recordsRef = FirebaseDatabase.instance.reference().child('records');
    FirebaseAuth.instance.authStateChanges().listen((User? user) {
      print("Auth State Change detected");
      if (user == null) {
        print('User is currently signed out!');
        try {
          // Cancel the records subscription (if we have one)
          _recordsSubscription.cancel();
        } catch (e) {}
      } else {
        print('User is signed in!');
        // setup the home page data listener
        _recordsSubscription = _recordsRef.onValue.listen((Event event) {
          print('Records: ${event.snapshot.value.toString()}');
        }); // Tell FutureBuilder we're ready to go...
      }
    });
    return true;
  }

  void _addRecord() async {
    late String userName;
    print("Adding record");
    userName = 'Unknown';
    // TODO: Fix this ASAP
    // All this does is prove that I don't truly understand null safety
    // in Dart/Flutter
    if (FirebaseAuth.instance.currentUser != null) {
      if (FirebaseAuth.instance.currentUser!.displayName != null) {
        userName = FirebaseAuth.instance.currentUser!.displayName!;
      }
    }
    Record record = Record(randomAlphaNumeric(20), userName,
        DateTime.now().millisecondsSinceEpoch);
    try {
      await _recordsRef.push().set(record.toJson());
    } catch (e) {
      print("Error: $e");
    }
  }

  @override
  void initState() {
    print('HomePage: initState()');
    isLoggedIn = false;
    super.initState();
    initFuture = initializeApp();
  }

  Widget recordList() {
    final DateFormat formatter = DateFormat('yyyy-MM-dd @ hh:mm:ss');

    return FirebaseAnimatedList(
      controller: _scrollController,
      query: getRecordsQuery(),
      // https://stackoverflow.com/questions/69377110/flutter-firebaseanimatedlist-assertion-error-on-push-in-sorted-list
      // https://github.com/FirebaseExtended/flutterfire/issues/7100
      sort: (a, b) {
        return a.value['timestamp'].compareTo(b.value['timestamp']);
      },
      itemBuilder: (context, snapshot, animation, index) {
        final json = snapshot.value as Map<dynamic, dynamic>;
        final record = Record.fromJson(json);
        var date = DateTime.fromMicrosecondsSinceEpoch(record.timestamp * 1000);
        return ListTile(
          title: Text("Message: ${record.msg}"),
          subtitle: Text("${record.user} (${formatter.format(date)}))"),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: initFuture,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            if (isLoggedIn) {
              return Scaffold(
                appBar: AppBar(title: Text(widget.title), actions: <Widget>[
                  IconButton(
                    icon: const Icon(Icons.logout_outlined),
                    onPressed: _doLogout,
                  ),
                ]),
                body: SafeArea(child: recordList()),
                floatingActionButton: FloatingActionButton(
                  onPressed: _addRecord,
                  tooltip: 'Add Record',
                  child: const Icon(Icons.add),
                ),
              );
            } else {
              return Scaffold(
                  appBar: AppBar(
                    title: Text(widget.title),
                  ),
                  body: SafeArea(
                      child: ListView(
                          padding: const EdgeInsets.all(16.0),
                          children: [
                        const Text(
                            "Access to this application is restricted to Authorized users only."),
                        const SizedBox(height: 15),
                        ConstrainedBox(
                          constraints: const BoxConstraints.tightFor(
                              width: 300, height: 75),
                          child: ElevatedButton(
                            child: const Text('Login'),
                            onPressed: _doLogin,
                          ),
                        )
                      ])));
            }
          } else {
            // Display the initialization message
            return Scaffold(
                appBar: AppBar(title: Text(widget.title)),
                body: const SafeArea(
                    child: Center(child: Text('Reading application data'))));
          } // if (!snapshot.hasData)
        });
  }
}

class Record {
  final String msg;
  final String user;
  final int timestamp;

  Record(this.msg, this.user, this.timestamp);

  Record.fromJson(Map<dynamic, dynamic> json)
      : msg = json['msg'],
        user = json['user'],
        timestamp = json['timestamp'];

  Map<dynamic, dynamic> toJson() =>
      {'msg': msg, 'user': user, 'timestamp': timestamp};
}
markusaksli-nc commented 3 years ago

By a minimal complete reproducible code sample I mean something we can just paste in main.dart and run to reproduce the issue. This example has too many other dependencies and obfuscates the issue a bit.

I tried reproducing this with just the official example but did not see the error.

johnwargo commented 3 years ago

My sincerest and most humble apologies for not delivering the smallest possible sample, I didn't know whether the issue was caused by Analytics or Crashes, so I left them in there. Here's a single file you can copy into a project's main.dart.

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_database/ui/firebase_animated_list.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:intl/intl.dart';
import 'package:random_string/random_string.dart';

const appName = 'Flutter Firebase Demo';
final GoogleSignIn _googleSignIn = GoogleSignIn();
late Future initFuture;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late Future<void> _initializeFlutterFireFuture;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: appName,
        debugShowCheckedModeBanner: false,
        theme: ThemeData.light(),
        darkTheme: ThemeData.dark(),
        home: FirebaseDemoHome(title: appName));
  }
}

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

  final String title;

  @override
  State<FirebaseDemoHome> createState() => _FirebaseDemoHomeState();
}

class _FirebaseDemoHomeState extends State<FirebaseDemoHome> {

  late DatabaseReference _recordsRef;
  late StreamSubscription<Event> _recordsSubscription;
  late bool isLoggedIn;

  final ScrollController _scrollController = ScrollController();

  Future<UserCredential> _signInWithGoogle() async {
    // Trigger the authentication flow
    final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
    // Obtain the auth details from the request
    final GoogleSignInAuthentication googleAuth =
        await googleUser!.authentication;
    // Create a new credential
    final credential = GoogleAuthProvider.credential(
      accessToken: googleAuth.accessToken,
      idToken: googleAuth.idToken,
    );
    // Once signed in, return the UserCredential
    return await FirebaseAuth.instance.signInWithCredential(credential);
  }

  _doLogin() {
    _signInWithGoogle().then((UserCredential theUser) {
      print("UID: ${theUser.user!.uid}");
      setState(() {
        isLoggedIn = theUser.user!.uid.isNotEmpty;
      });
    });
  }

  _doLogout() {
    // logs out of Firebase and Google
    FirebaseAuth.instance.signOut().then((value) {
      _googleSignIn.disconnect();
      setState(() {
        isLoggedIn = (FirebaseAuth.instance.currentUser != null);
      });
    }).catchError((error) {
      print("Unable to logout");
      print(error);
    });
  }

  // https://www.raywenderlich.com/24346128-firebase-realtime-database-tutorial-for-flutter
  Query getRecordsQuery() {
    return _recordsRef;
  }

  // Initialize the Config class (loading data) for the FutureBuilder
  Future<bool> initializeApp() async {
    print('HomePage: initializeApp()');
    // Initialize the Records database (Firebase)
    _recordsRef = FirebaseDatabase.instance.reference().child('records');
    FirebaseAuth.instance.authStateChanges().listen((User? user) {
      print("Auth State Change detected");
      if (user == null) {
        print('User is currently signed out!');
        try {
          // Cancel the records subscription (if we have one)
          _recordsSubscription.cancel();
        } catch (e) {}
      } else {
        print('User is signed in!');
        // setup the home page data listener
        _recordsSubscription = _recordsRef.onValue.listen((Event event) {
          print('Records: ${event.snapshot.value.toString()}');
        }); // Tell FutureBuilder we're ready to go...
      }
    });
    return true;
  }

  void _addRecord() async {
    late String userName;
    print("Adding record");
    userName = 'Unknown';
    // TODO: Fix this ASAP
    // All this does is prove that I don't truly understand null safety
    // in Dart/Flutter
    if (FirebaseAuth.instance.currentUser != null) {
      if (FirebaseAuth.instance.currentUser!.displayName != null) {
        userName = FirebaseAuth.instance.currentUser!.displayName!;
      }
    }
    Record record = Record(randomAlphaNumeric(20), userName,
        DateTime.now().millisecondsSinceEpoch);
    try {
      await _recordsRef.push().set(record.toJson());
    } catch (e) {
      print("Error: $e");
    }
  }

  @override
  void initState() {
    print('HomePage: initState()');
    isLoggedIn = false;
    super.initState();
    initFuture = initializeApp();
  }

  Widget recordList() {
    final DateFormat formatter = DateFormat('yyyy-MM-dd @ hh:mm:ss');

    return FirebaseAnimatedList(
      controller: _scrollController,
      query: getRecordsQuery(),
      // https://stackoverflow.com/questions/69377110/flutter-firebaseanimatedlist-assertion-error-on-push-in-sorted-list
      // https://github.com/FirebaseExtended/flutterfire/issues/7100
      sort: (a, b) {
        return a.value['timestamp'].compareTo(b.value['timestamp']);
      },
      itemBuilder: (context, snapshot, animation, index) {
        final json = snapshot.value as Map<dynamic, dynamic>;
        final record = Record.fromJson(json);
        var date = DateTime.fromMicrosecondsSinceEpoch(record.timestamp * 1000);
        return ListTile(
          title: Text("Message: ${record.msg}"),
          subtitle: Text("${record.user} (${formatter.format(date)}))"),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: initFuture,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            if (isLoggedIn) {
              return Scaffold(
                appBar: AppBar(title: Text(widget.title), actions: <Widget>[
                  IconButton(
                    icon: const Icon(Icons.logout_outlined),
                    onPressed: _doLogout,
                  ),
                ]),
                body: SafeArea(child: recordList()),
                floatingActionButton: FloatingActionButton(
                  onPressed: _addRecord,
                  tooltip: 'Add Record',
                  child: const Icon(Icons.add),
                ),
              );
            } else {
              return Scaffold(
                  appBar: AppBar(
                    title: Text(widget.title),
                  ),
                  body: SafeArea(
                      child: ListView(
                          padding: const EdgeInsets.all(16.0),
                          children: [
                        const Text(
                            "Access to this application is restricted to Authorized users only."),
                        const SizedBox(height: 15),
                        ConstrainedBox(
                          constraints: const BoxConstraints.tightFor(
                              width: 300, height: 75),
                          child: ElevatedButton(
                            child: const Text('Login'),
                            onPressed: _doLogin,
                          ),
                        )
                      ])));
            }
          } else {
            // Display the initialization message
            return Scaffold(
                appBar: AppBar(title: Text(widget.title)),
                body: const SafeArea(
                    child: Center(child: Text('Reading application data'))));
          } // if (!snapshot.hasData)
        });
  }
}

class Record {
  final String msg;
  final String user;
  final int timestamp;

  Record(this.msg, this.user, this.timestamp);

  Record.fromJson(Map<dynamic, dynamic> json)
      : msg = json['msg'],
        user = json['user'],
        timestamp = json['timestamp'];

  Map<dynamic, dynamic> toJson() =>
      {'msg': msg, 'user': user, 'timestamp': timestamp};
}

When I try to add a document to the collection with the sort in place, it crashes with the following error:

I/flutter (17228): Adding record
E/flutter (17228): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: 'package:flutter/src/widgets/animated_list.dart': Failed assertion: line 959 pos 12: 'index != null && index >= 0': is not true.
E/flutter (17228): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39)
E/flutter (17228): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
E/flutter (17228): #2      SliverAnimatedListState.insertItem (package:flutter/src/widgets/animated_list.dart:959:12)
E/flutter (17228): #3      AnimatedListState.insertItem (package:flutter/src/widgets/animated_list.dart:484:42)
E/flutter (17228): #4      FirebaseAnimatedListState._onChildAdded (package:firebase_database/ui/firebase_animated_list.dart:175:36)
E/flutter (17228): #5      FirebaseSortedList._onChildAdded (package:firebase_database/ui/firebase_sorted_list.dart:97:18)
E/flutter (17228): #6      _rootRunUnary (dart:async/zone.dart:1436:47)
E/flutter (17228): #7      _CustomZone.runUnary (dart:async/zone.dart:1335:19)
E/flutter (17228): #8      _CustomZone.runUnaryGuarded (dart:async/zone.dart:1244:7)
E/flutter (17228): #9      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:341:11)
E/flutter (17228): #10     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:271:7)
E/flutter (17228): #11     _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:123:11)
E/flutter (17228): #12     _MapStream._handleData (dart:async/stream_pipe.dart:218:10)
E/flutter (17228): #13     _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:153:13)
E/flutter (17228): #14     _rootRunUnary (dart:async/zone.dart:1436:47)
E/flutter (17228): #15     _CustomZone.runUnary (dart:async/zone.dart:1335:19)
E/flutter (17228): #16     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1244:7)
E/flutter (17228): #17     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:341:11)
E/flutter (17228): #18     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:271:7)
E/flutter (17228): #19     _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:123:11)
E/flutter (17228): #20     _HandleErrorStream._handleData (dart:async/stream_pipe.dart:253:10)
E/flutter (17228): #21     _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:153:13)
E/flutter (17228): #22     _rootRunUnary (dart:async/zone.dart:1436:47)
E/flutter (17228): #23     _CustomZone.runUnary (dart:async/zone.dart:1335:19)
E/flutter (17228): #24     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1244:7)
E/flutter (17228): #25     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:341:11)
E/flutter (17228): #26     _DelayedData.perform (dart:async/stream_impl.dart:591:14)
E/flutter (17228): #27     _StreamImplEvents.handleNext (dart:async/stream_impl.dart:706:11)
E/flutter (17228): #28     _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:663:7)
E/flutter (17228): #29     _rootRun (dart:async/zone.dart:1420:47)
E/flutter (17228): #30     _CustomZone.run (dart:async/zone.dart:1328:19)
E/flutter (17228): #31     _CustomZone.runGuarded (dart:async/zone.dart:1236:7)
E/flutter (17228): #32     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1276:23)
E/flutter (17228): #33     _rootRun (dart:async/zone.dart:1428:13)
E/flutter (17228): #34     _CustomZone.run (dart:async/zone.dart:1328:19)
E/flutter (17228): #35     _CustomZone.runGuarded (dart:async/zone.dart:1236:7)
E/flutter (17228): #36     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1276:23)
E/flutter (17228): #37     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
E/flutter (17228): #38     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)

If I comment out the sort, it works as expected.

I left the login details in there, because the whole point of this partcular application is to use Auth and Realtime database and my Firebase project has the following realtime database access rules:

{
  "rules": {
     "records": {
        ".read": "auth != null",
        ".write": "auth != null"
    }
  }
}

but that shouldn't affect the operation of the sort method.

Greatcallie commented 3 years ago

I have the same issue. And I have the FirebaseAnimatedList and sort and once there is a push on the database, I get an error

johnwargo commented 3 years ago

yeah, it sure feels like a bug.

markusaksli-nc commented 3 years ago

Reproduces with firebase_database: ^8.0.0 https://github.com/FirebaseExtended/flutterfire/blob/110a79c5505dc818ba017cc4a46144170830f352/packages/firebase_database/firebase_database/lib/ui/firebase_sorted_list.dart#L97 indexOf is returning -1 here

Minimal reproducible code sample ```dart import 'dart:async'; import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/ui/firebase_animated_list.dart'; const appName = 'Flutter Firebase Demo'; late Future initFuture; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( title: appName, debugShowCheckedModeBanner: false, theme: ThemeData.light(), darkTheme: ThemeData.dark(), home: FirebaseDemoHome(title: appName)); } } class FirebaseDemoHome extends StatefulWidget { const FirebaseDemoHome({Key? key, required this.title}) : super(key: key); final String title; @override State createState() => _FirebaseDemoHomeState(); } class _FirebaseDemoHomeState extends State { late DatabaseReference _recordsRef; late StreamSubscription _recordsSubscription; late bool isLoggedIn; final ScrollController _scrollController = ScrollController(); // https://www.raywenderlich.com/24346128-firebase-realtime-database-tutorial-for-flutter Query getRecordsQuery() { return _recordsRef; } // Initialize the Config class (loading data) for the FutureBuilder Future initializeApp() async { print('HomePage: initializeApp()'); // Initialize the Records database (Firebase) _recordsRef = FirebaseDatabase.instance.reference().child('records'); return true; } void _addRecord() async { late String userName; print("Adding record"); // TODO: Fix this ASAP Record record = Record(Random().nextDouble().toString(), DateTime.now().millisecondsSinceEpoch); try { await _recordsRef.push().set(record.toJson()); } catch (e) { print("Error: $e"); } } @override void initState() { print('HomePage: initState()'); isLoggedIn = false; super.initState(); initFuture = initializeApp(); } Widget recordList() { return FirebaseAnimatedList( controller: _scrollController, query: getRecordsQuery(), // https://stackoverflow.com/questions/69377110/flutter-firebaseanimatedlist-assertion-error-on-push-in-sorted-list // https://github.com/FirebaseExtended/flutterfire/issues/7100 sort: (a, b) { return a.value['timestamp'].compareTo(b.value['timestamp']); }, itemBuilder: (context, snapshot, animation, index) { final json = snapshot.value as Map; final record = Record.fromJson(json); var date = DateTime.fromMicrosecondsSinceEpoch(record.timestamp * 1000); return ListTile( title: Text("Message: ${record.msg}"), subtitle: Text("$date"), ); }, ); } @override Widget build(BuildContext context) { return FutureBuilder( future: initFuture, builder: (context, snapshot) { if (snapshot.hasData) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: SafeArea(child: recordList()), floatingActionButton: FloatingActionButton( onPressed: _addRecord, tooltip: 'Add Record', child: const Icon(Icons.add), ), ); } else { // Display the initialization message return Scaffold( appBar: AppBar(title: Text(widget.title)), body: const SafeArea( child: Center(child: Text('Reading application data')))); } // if (!snapshot.hasData) }); } } class Record { final String msg; final int timestamp; Record(this.msg, this.timestamp); Record.fromJson(Map json) : msg = json['msg'], timestamp = json['timestamp']; Map toJson() => {'msg': msg, 'timestamp': timestamp}; } ```
flutter doctor -v ``` [√] Flutter (Channel master, 2.6.0-12.0.pre.238, on Microsoft Windows [Version 10.0.19043.1237], locale en-GB) • Flutter version 2.6.0-12.0.pre.238 at C:\Development\flutter_master • Upstream repository https://github.com/flutter/flutter.git • Framework revision bf429f2771 (5 hours ago), 2021-10-06 21:44:27 -0700 • Engine revision e914da14f1 • Dart version 2.15.0 (build 2.15.0-178.0.dev) [√] Android toolchain - develop for Android devices (Android SDK version 31.0.0) • Android SDK at C:\Users\marku\AppData\Local\Android\sdk • Platform android-31, build-tools 31.0.0 • Java binary at: C:\Users\marku\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-0\203.7678000\jre\bin\java • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7249189) • All Android licenses accepted. [√] Chrome - develop for the web • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe [√] Visual Studio - develop for Windows (Visual Studio Community 2019 16.11.2) • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community • Visual Studio Community 2019 version 16.11.31624.102 • Windows 10 SDK version 10.0.19041.0 [√] Android Studio (version 2020.3) • Android Studio at C:\Users\marku\AppData\Local\JetBrains\Toolbox\apps\AndroidStudio\ch-0\203.7678000 • Flutter plugin version 61.2.2 • Dart plugin version 203.8430 • Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7249189) [√] Connected device (4 available) • SM G950F (mobile) • ce12171c51cc001c03 • android-arm64 • Android 9 (API 28) • Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.19043.1237] • Chrome (web) • chrome • web-javascript • Google Chrome 94.0.4606.71 • Edge (web) • edge • web-javascript • Microsoft Edge 93.0.961.52 • No issues found! ```
Greatcallie commented 3 years ago

Any workaround for this? I need to update my app asap. FlutterFire please show love to this package!

Greatcallie commented 3 years ago

It works fine on release build. Can someone else confirm that?

maksym-ostrovyj-ew commented 3 years ago

It works fine on release build. Can someone else confirm that?

Unfortunately, I have the same error on the release build too.

JudeAlquiza commented 3 years ago

Hi I'm also experiencing the same issue for firebase_database: ^8.0.1

[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: 'package:flutter/src/widgets/animated_list.dart': Failed assertion: line 959 pos 12: 'index != null && index >= 0': is not true. package:flutter/…/widgets/animated_list.dart:1 E/flutter ( 5515): # 0 _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39) E/flutter ( 5515): # 1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5) E/flutter ( 5515): # 2 SliverAnimatedListState.insertItem package:flutter/…/widgets/animated_list.dart:959 E/flutter ( 5515): # 3 AnimatedListState.insertItem package:flutter/…/widgets/animated_list.dart:484 E/flutter ( 5515): # 4 FirebaseAnimatedListState._onChildAdded package:firebase_database/ui/firebase_animated_list.dart:175 E/flutter ( 5515): # 5 FirebaseSortedList._onChildAdded package:firebase_database/ui/firebase_sorted_list.dart:97

any news on the bug fix ? thanks in advance :)

lesnitsky commented 3 years ago

This widget will be dropped from firebase_database plugin. A successor would be implemented as a part of firebase_ui instead.

johnwargo commented 3 years ago

@lesnitsky can you please provide more detail? Dropped when? Successor implemented when? Is the team going to fix this now or do we have to wait for firebase_UI?

We're trying to get this working in the apps we have now, so future plans don't help us very much without the right context.

abdop commented 2 years ago

I'm having the same issue, Does FirebaseUI replaced this widget?

E/flutter ( 3187): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: 'package:flutter/src/widgets/animated_list.dart': Failed assertion: line 959 pos 12: 'index != null && index >= 0': is not true. package:flutter/…/widgets/animated_list.dart:1

johnwargo commented 2 years ago

@lesnitsky thanks for the drive-by promoting your new project but I don't see how your comment directly affects us now and it doesn't help us resolve the issue with the current library.

@markusaksli-nc its been a while, anything being done on this issue?

rakibjoarder commented 2 years ago

Reproduces with firebase_database: ^8.0.0 flutterfire/packages/firebase_database/firebase_database/lib/ui/firebase_sorted_list.dart

Line 97 in 110a79c

onChildAdded!(_snapshots.indexOf(event.snapshot), event.snapshot);

indexOf is returning -1 here

solution is here:

`void _onChildAdded(DatabaseEvent event) { //adding this will solve your issue DataSnapshot dataSnapshot = event.snapshot as DataSnapshot;

_snapshots.add(dataSnapshot); _snapshots.sort(comparator);`

onChildAdded(_snapshots.indexOf(dataSnapshot), dataSnapshot); }

deremakif commented 2 years ago

Same issue here!

crcdng commented 2 years ago

still broken in 9.0.15

johnwargo commented 2 years ago

@markusaksli-nc what happened? Will someone look at this?

rakibjoarder commented 2 years ago

@johnwargo check my last comment for solution.