aws-amplify / amplify-flutter

A declarative library with an easy-to-use interface for building Flutter applications on AWS.
https://docs.amplify.aws
Apache License 2.0
1.31k stars 242 forks source link

Receive "ERROR | WebSocketBloc | Shutting down with exception: NetworkException" after upgrading from Amplify Flutter 1.6 to 2.3 #5184

Closed stephenjen closed 2 weeks ago

stephenjen commented 1 month ago

Description

Started to get the following error after upgrading from Amplify Flutter 1.6 to 2.3:

`flutter: ERROR | WebSocketBloc | Shutting down with exception: NetworkException { "message": "Exception from WebSocketService.", "underlyingException": "type '_Map<String, dynamic>' is not a subtype of type 'List?' in type cast" } [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: Cannot add new events after calling close

0 _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:243:24)

1 WebSocketBloc._emit (package:amplify_api_dart/src/graphql/web_socket/blocs/web_socket_bloc.dart:134:24)

2 WebSocketBloc._shutdownWithException (package:amplify_api_dart/src/graphql/web_socket/blocs/web_socket_bloc.dart:549:5)

3 WebSocketBloc._init. (package:amplify_api_dart/src/graphql/web_socket/blocs/web_socket_bloc.dart:283:9)

4 _rootRunBinary (dart:async/zone.dart:1423:47)

5 _CustomZone.runBinary (dart:async/zone.dart:1315:19)

6 _CustomZone.runBinaryGuarded (dart:async/zone.dart:1225:7)

7 _BufferingStreamSubscription._sendError.sendError (dart:async/stream_impl.dart:384:15)

8 _BufferingStreamSubscription._sendError (dart:async/stream_impl.dart:402:7)

9 _BufferingStreamSubscription.addError (dart:async/stream<…>

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: Cannot add new events after calling close

0 _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:243:24)

1 WebSocketBloc._emit (package:amplify_api_dart/src/graphql/web_socket/blocs/web_socket_bloc.dart:134:24)

2 WebSocketBloc._shutdownWithException (package:amplify_api_dart/src/graphql/web_socket/blocs/web_socket_bloc.dart:549:5)

3 WebSocketBloc._init. (package:amplify_api_dart/src/graphql/web_socket/blocs/web_socket_bloc.dart:283:9)

4 _rootRunBinary (dart:async/zone.dart:1423:47)

5 _CustomZone.runBinary (dart:async/zone.dart:1315:19)

6 _CustomZone.runBinaryGuarded (dart:async/zone.dart:1225:7)

7 _BufferingStreamSubscription._sendError.sendError (dart:async/stream_impl.dart:384:15)

8 _BufferingStreamSubscription._sendError (dart:async/stream_impl.dart:402:7)

9 _BufferingStreamSubscription.addError (dart:async/stream<…>`

Categories

Steps to Reproduce

Prior to this error, the following is run:

  1. Authenticator - user logs in
  2. await Amplify.DataStore.stop(), then wait 1 sec and await Amplify.DataStore.start();
  3. Set a listener for sync progress dbListener = Amplify.Hub.listen(HubChannel.DataStore, (dynamic hubEvent) async {}

Screenshots

No response

Platforms

Flutter Version

3.22.3

Amplify Flutter Version

2.3.0

Deployment Method

Amplify CLI

Schema

No response

Equartey commented 1 month ago

Hi @stephenjen, thanks for reporting.

Can you provide your GraphQL schema?

Are you able to sync models at any point? For example, a fresh app start with a stored user session?

stephenjen commented 1 month ago

Here's my schema.graphql:

GraphQL Schema ```graphql type Tag @model @auth(rules: [{allow: private}]) { id: ID! name: String! @index(name: "byName") scheduledBlocks: [ScheduledBlock] @manyToMany(relationName: "ScheduledBlockTag") trayBlocks: [TrayBlock] @hasMany(indexName: "TrayBlockTag") seriess: [Series] @manyToMany(relationName: "SeriesTag") metrics: [Metric] @manyToMany(relationName: "MetricTag") userStatsPerformanceModeMetricsAdherence: [UserStatsPerformanceModeMetricsAdherence] @hasMany(indexName: "byTag", fields: ["id"]) } type ScheduledBlock @model @auth(rules: [{ allow: owner, ownerField: "pOwner" }{ allow: private, operations: [read] }]) { id: ID! title: String! startTime: AWSDateTime! endTime: AWSDateTime! duration: Float tags: [Tag] @manyToMany(relationName: "ScheduledBlockTag") trayBlockID: ID @index(name: "byTrayBlock", sortKeyFields: ["title"]) # problem trayBlock: TrayBlock @belongsTo(fields: ["trayBlockID"]) # problem pOwner: String } type Metric @model @auth(rules: [{allow: private}]) { id: ID! title: String! duration: Int! priority: Int! operationModeID: ID! @index(name: "byOperationMode", sortKeyFields: ["title"]) operationMode: OperationMode! @belongsTo(fields: ["operationModeID"]) tags: [Tag] @manyToMany(relationName: "MetricTag") } type UserOperationMode @model @auth(rules: [{ allow: owner, ownerField: "pOwner" }{ allow: private, operations: [read] }]) { id: ID! date: AWSDateTime! operationModeID: ID @index(name: "byOperationModeUserOperationMode", sortKeyFields: ["date"]) # problem operationMode: OperationMode @belongsTo(fields: ["operationModeID"]) # problem pOwner: String! test: String } type Settings @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) { id: ID! name: String! value: String! pOwner: String! } type OperationMode @model @auth(rules: [{allow: private}]) { id: ID! title: String! shortDescription: String description: String! longDescription: String featured: Boolean! featuredPriority: Int! category: String! categoryPriority: Int! metric: [Metric] @hasMany(indexName: "byOperationMode", fields: ["id"]) userOperationMode: [UserOperationMode] @hasMany(indexName: "byOperationModeUserOperationMode", fields: ["id"]) series: [Series] @hasMany(indexName: "bySeries", fields: ["id"]) trayBlocks: [TrayBlock] @manyToMany(relationName: "OperationModeTrayBlock") # problem profileImage: String featuredProfileImage: String authorID: ID @index(name: "byAuthor", sortKeyFields: ["title"]) author: Author @belongsTo(fields: ["authorID"]) shares: [Share] @manyToMany(relationName: "ShareOperationMode") activities: [Activity] @hasMany(indexName: "byOperationMode", fields: ["id"]) stats: [UserStatsOperationMode] @hasMany(indexName: "byOperationMode", fields: ["id"]) # problem } type Author @model @auth(rules: [{allow: private}]) { id: ID! fname: String! lname: String! mname: String description: String profileImage: String operationModes: [OperationMode] @hasMany(indexName: "byAuthor", fields: ["id"]) } type Series @model @auth(rules: [{allow: private}]) { id: ID! title: String! description: String! longDescription: String! tags: [Tag] @manyToMany(relationName: "SeriesTag") operationModeID: ID! @index(name: "bySeries", sortKeyFields: ["title"]) operationMode: OperationMode! @belongsTo(fields: ["operationModeID"]) shares: [Share] @manyToMany(relationName: "ShareSeries") trayBlocks: [SeriesTrayBlock] @hasMany(indexName: "bySeriesSeriesTrayBlockA", fields: ["id"]) activities: [Activity] @hasMany(indexName: "bySeriesActivity", fields: ["id"]) recommendedStartTime: String recommendedStartTimeTime: AWSDateTime recommendedEndTimeTime: AWSDateTime featured: Boolean featuredPriority: Int duration: String recommendedTimeOfDay: String image: String } type TrayBlock @model @auth(rules: [{allow: private}]) { id: ID! title: String! description: String! longDescription: String oneWordDescription: String duration: Int! tagID: ID! @index(name: "TrayBlockTag", sortKeyFields: ["id"]) tag: Tag! @belongsTo(fields: ["tagID"]) seriess: [SeriesTrayBlock] @hasMany(indexName: "byTrayBlockSeriesA", fields: ["id"]) operationModes: [OperationMode] @manyToMany(relationName: "OperationModeTrayBlock") # problem scheduledBlocks: [ScheduledBlock] @hasMany(indexName: "byTrayBlock", fields: ["id"]) # problem } type SeriesTrayBlock @model @auth(rules: [{allow: private}]) { id: ID! sequence: Int seriesID: ID! @index(name: "bySeriesSeriesTrayBlockA", sortKeyFields:["id"]) trayBlockID: ID! @index(name: "byTrayBlockSeriesA", sortKeyFields:["id"]) series: Series @belongsTo(fields: ["seriesID"]) trayBlock: TrayBlock @belongsTo(fields: ["trayBlockID"]) } type Friend @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) { id: ID! profileID: ID @index(name: "byProfile", sortKeyFields: ["id"]) # had to remove the ! here for activity to work profile: Profile @belongsTo(fields: ["profileID"]) # had to remove the ! here for activity to work activities: [Activity] @hasMany(indexName: "byFriend", fields: ["id"]) approved: Boolean blocked: Boolean pOwner: String! } enum ActivityAction { STARTEDSCHEDULING ADDEDFRIEND CHANGEDOPERATIONMODE COMPLETIONPROGRESS USEDSERIES ACHIEVEDGOALS ALMOSTACHEIVEDGOALS UPLOADEDIMAGE MODIFIEDSCHEDULE MODIFIEDSCHEDULEMAJOR LIKEDACTIVITY LIKEDSERIES LIKEDBLOCK LIKEDACHIEVEDGOALS LIKEDCOMMENT LIKEDUPLOAD COMMENTEDACTIVITY COMMENTEDSERIES COMMENTEDBLOCK COMMENTEDACHEIVEDGOALS COMMENTEDALMOSTACHEIVEDGOALS } type Activity @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) { id: ID! date: AWSDateTime! action: ActivityAction! friendID: ID! @index(name: "byFriend", sortKeyFields: ["date"]) friend: Friend! @belongsTo(fields: ["friendID"]) friendsListID: ID @index(name: "byFriendsList", sortKeyFields: ["id"]) friendsList: FriendsList @belongsTo(fields: ["friendsListID"]) operationModeID: ID @index(name: "byOperationMode", sortKeyFields: ["date"]) operationMode: OperationMode @belongsTo(fields: ["operationModeID"]) comments: [Comment] @hasMany(indexName: "byActivityC", fields: ["id"]) seriesID: ID @index(name: "bySeriesActivity", sortKeyFields: ["date"]) series: Series @belongsTo(fields: ["seriesID"]) pOwnerProfileID: ID @index(name: "activitiesByProfile", sortKeyFields: ["date"]) pOwnerProfile: Profile @belongsTo(fields: ["pOwnerProfileID"]) completionPercentages: AWSJSON countedSchedules: [String!] pOwner: String! } type Like @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) { id: ID! shareID: ID! @index(name: "byShare", sortKeyFields: ["id"]) share: Share! @belongsTo(fields: ["shareID"]) content: String pOwner: String! } type Comment @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) { id: ID! activityID: ID! @index(name: "byActivityC", sortKeyFields: ["id"]) activity: Activity! @belongsTo(fields: ["activityID"]) content: String! pOwner: String! } type Profile @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) { id: ID! userName: String fname: String lname: String profileImage: String remoteImageURL: String bio: String pOwner: String! friends: [Friend] @hasMany(indexName: "byProfile", fields: ["id"]) activities: [Activity] @hasMany(indexName: "activitiesByProfile", fields: ["id"]) shares: [Share] @hasMany(indexName: "sharesByProfile", fields: ["id"]) onboarded: Boolean } type UserStatsPerformanceModeCategoriesUsed @model @auth(rules: [{ allow: owner, ownerField: "pOwner" }{ allow: private, operations: [read] }]) { id: ID! pOwner: String! lastUpdated: AWSDateTime! year: Int! category: String! m1: Int m2: Int m3: Int m4: Int m5: Int m6: Int m7: Int m8: Int m9: Int m10: Int m11: Int m12: Int } type UserStatsDaysScheduled @model @auth(rules: [{ allow: owner, ownerField: "pOwner" }{ allow: private, operations: [read] }]) { id: ID! pOwner: String! year: Int! lastUpdated: AWSDateTime! m1: Int m2: Int m3: Int m4: Int m5: Int m6: Int m7: Int m8: Int m9: Int m10: Int m11: Int m12: Int weeklyStats: String } type UserStatsOperationMode @model @auth(rules: [{ allow: owner, ownerField: "pOwner" }{ allow: private, operations: [read] }]) { id: ID! pOwner: String! operationModeID: ID @index(name: "byOperationMode", sortKeyFields: ["lastUpdated"]) operationMode: OperationMode @belongsTo(fields: ["operationModeID"]) lastUpdated: AWSDateTime! year: Int m1: Int m2: Int m3: Int m4: Int m5: Int m6: Int m7: Int m8: Int m9: Int m10: Int m11: Int m12: Int } type UserStatsStreaks @model @auth(rules: [{ allow: owner, ownerField: "pOwner" }{ allow: private, operations: [read] }]) { id: ID! pOwner: String! year: Int! lastScheduledDayOverall: AWSDateTime m1CountOverall: Int m2CountOverall: Int m3CountOverall: Int m4CountOverall: Int m5CountOverall: Int m6CountOverall: Int m7CountOverall: Int m8CountOverall: Int m9CountOverall: Int m10CountOverall: Int m11CountOverall: Int m12CountOverall: Int m1LongestStreakOverall: Int m2LongestStreakOverall: Int m3LongestStreakOverall: Int m4LongestStreakOverall: Int m5LongestStreakOverall: Int m6LongestStreakOverall: Int m7LongestStreakOverall: Int m8LongestStreakOverall: Int m9LongestStreakOverall: Int m10LongestStreakOverall: Int m11LongestStreakOverall: Int m12LongestStreakOverall: Int } type UserStatsPerformanceModeMetricsAdherence @model @auth(rules: [{ allow: owner, ownerField: "pOwner" }{ allow: private, operations: [read] }]) { id: ID! pOwner: String! year: Int! tagID: ID! @index(name: "byTag", sortKeyFields: ["year"]) tag: Tag! @belongsTo(fields: ["tagID"]) m1MetricQuota: Int m1MetricScheduled: Int m2MetricQuota: Int m2MetricScheduled: Int m3MetricQuota: Int m3MetricScheduled: Int m4MetricQuota: Int m4MetricScheduled: Int m5MetricQuota: Int m5MetricScheduled: Int m6MetricQuota: Int m6MetricScheduled: Int m7MetricQuota: Int m7MetricScheduled: Int m8MetricQuota: Int m8MetricScheduled: Int m9MetricQuota: Int m9MetricScheduled: Int m10MetricQuota: Int m10MetricScheduled: Int m11MetricQuota: Int m11MetricScheduled: Int m12MetricQuota: Int m12MetricScheduled: Int } type Share @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) { id: ID! sharedDate: AWSDateTime! completionDate: AWSDateTime! note: String seriess: [Series] @manyToMany(relationName: "ShareSeries") operationModes: [OperationMode] @manyToMany(relationName: "ShareOperationMode") numberOfBlocks: Int completionPercentages: AWSJSON likes: [Like] @hasMany(indexName: "byShare", fields: ["id"]) pOwnerProfileID: ID @index(name: "sharesByProfile", sortKeyFields: ["sharedDate"]) pOwnerProfile: Profile @belongsTo(fields: ["pOwnerProfileID"]) imageURL: String pOwner: String! } type FriendsList @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) { id: ID! friendsIds: [String!]! activities: [Activity] @hasMany(indexName: "byFriendsList", fields: ["id"]) pOwner: String! } ```

I just noticed that in my model/ directory for some of these models I see the following:

ShareSeries.fromJson(Map<String, dynamic> json)

Not sure if the Map<String, dynamic> is the '_Map<String, dynamic>' from the error message.

After I hot restart, I still get the error and the sync starts.

stephenjen commented 1 month ago

I think I see were the error is occurring. As I mentioned before, I have the following code:

await Amplify.DataStore.stop();
await Future.delayed(const Duration(seconds: 1));
await Amplify.DataStore.start();

Before I was able to call await Amplify.DataStore.stop(); regardless if DataStore has already .start() or not, but now it throw the error in my original post. Removing await Amplify.DataStore.stop(); and just calling await Amplify.DataStore.start(); seems to be the fix. Incidentally, starting the DataStore if it has already been started also causes the error in my original post.

Question - how can I determine if DataStore is already running?

Jordan-Nelson commented 1 month ago

@stephenjen - I don't believe there is a way to check if DataStore is running. Calling any data store method (start, query, observe, etc.) other than .clear() and .stop() will automatically start DataStore if it is not already running. .clear() and .stop() will stop it. So if you have called a DataStore method without calling clear/stop, it will be running.

Jordan-Nelson commented 1 month ago

Am I correct in understanding that to reproduce this issue you can do either of the following?

  1. call DataStore.stop() when DataStore is not currently running
  2. call DataStore.start() when DataStore is already running
stephenjen commented 1 month ago

@Jordan-Nelson that seems to be the case, but I'm investigating the reason why when user logout and log back in the error occurs no matter what.

Jordan-Nelson commented 1 month ago

@stephenjen Thanks. We will attempt to reproduce this. I will let you know if we need any more info.

harrynguyen2510 commented 1 month ago

any update?

stephenjen commented 1 month ago

@Jordan-Nelson I've removed all DataStore.stop() and DataStore.start(), and only have a DataStore.query() query statement to start up DataStore and a DataStore.clear() after user logs out.

On a fresh install of the app, after user logs in DataStore starts up and completes sync without errors, but after logging out and logging back in, the same code is run, I get the errors in OP.

harrynguyen2510 commented 1 month ago

@stephenjen In my case I call .stop and .start when App.resumed. Because I want to trigger 'ready' Datastore event from Amplify.Hub.listen(HubChannel.DataStore...), then I got this error How do you handle when app resumes with Datastore, making sure all data is ready?

Jordan-Nelson commented 1 month ago

So far I have been unable to reproduce this, but I am still working on it.

@harrynguyen2510 do you see the same exception as in the original issue description?

Jordan-Nelson commented 1 month ago

@stephenjen - I did not realize that you had also opened https://github.com/aws-amplify/amplify-flutter/issues/5143, which looks similar. I see you were previously using version 1.6 and were seeing an issue where data was not syncing. Then after upgrading to version 2.3, in addition to data not syncing, you see this error message. Is that accurate?

Starting with version 2.2, API requests from within DataStore (on iOS) are made from dart. Prior to this version they were made from native iOS code. The logs from native iOS code do not show unless you are debugging from Xcode, whereas the logs from dart code will be visible in a regular flutter debug session (from VS Code, Android Studio, or the terminal).

It sounds like the issue you are facing on version 1.6 and 2.3 may be the same. However, you are seeing additional logs when using version 2.3 since the calls are made from Dart. Would you agree with this? If so, I may close out https://github.com/aws-amplify/amplify-flutter/issues/5143 in favor of this issue so that we can track the issue in once location.

stephenjen commented 1 month ago

Hi @harrynguyen2510 , I'm not calling Amplify.DataStore.start() but am using Amplify.DataStore.query(Tag.classType), and I'm able to receive the following two events for switch (hubEvent.eventName):

case 'ready'
case 'syncQueriesReady'

If you're not hitting these cases, I'd double check if sync has indeed completed. I had the problem before where suspected corrupted data caused sync to not finish and one of the above two cases where never hit. Perhaps try printing out msg.modelName, mse.added and mse.updated for case case 'modelSynced' just to double check all models have indeed been synced:

case 'modelSynced':
      ModelSyncedEvent mse = hubEvent.payload as ModelSyncedEvent;
      String modelName = mse.modelName;

      msg = 'Amplify hub event - ${mse.modelName} has been sync\'d from cloud, ${mse.added}, ${mse.updated} ...';
      break;
stephenjen commented 1 month ago

Hi @Jordan-Nelson,

Thanks for sharing additional details of the changes from 1.6 to 2.2. So far, I can lean towards stating these two issues seem unrelated but possibly can both be fixed by the upgrade. I have not been able to test the original issue 5143 that prompted me to upgrade, as after the upgrade I started to encounter the issue reported in this ticket.

stephenjen commented 1 month ago

@Jordan-Nelson, got some interesting updates to share.

After spending time progressively commenting out more and more fields and models to try to get to a better understanding of what may be causing this error, I realized the error doesn't seem to be related to any particular fields or models, but to the total number of models in my schema.graphql.

I created 17 identical models:

  type Tag @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) {
      id: ID!
      title: String!
      pOwner: String!
  }

  type Tag1 @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) {
      id: ID!
      title: String!
      pOwner: String!
  }

  type Tag2 @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) {
      id: ID!
      title: String!
      pOwner: String!
  }
  ...
  ...
  ...
  type Tag16 @model @auth(rules: [{ allow: private, ownerField: "pOwner" }]) {
      id: ID!
      title: String!
      pOwner: String!
  }

With 17 models I get the error, but removing the 17th model then I don't get the error.

Here's the code I used to test this:

import 'dart:async';
import 'dart:ui';

import 'package:amplify_api/amplify_api.dart';
import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
import 'package:amplify_authenticator/amplify_authenticator.dart';
import 'package:amplify_storage_s3/amplify_storage_s3.dart';
import 'package:flutter/material.dart';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:amplify_datastore/amplify_datastore.dart';
import 'package:rectangles/amplifyconfiguration.dart';
import 'models/ModelProvider.dart';

void main() => runApp(MyApp());

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

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

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return Authenticator(
      child: MaterialApp(
        title: 'Test',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

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

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

class MyHomePageState extends State<MyHomePage> {
  late StreamSubscription<DataStoreHubEvent> dbListener;
  late var subscription;

  bool networkIsUp = false;

  Future<void> observeEvents() async {
    print('inside observeEvents');

    dbListener = Amplify.Hub.listen(HubChannel.DataStore, (dynamic hubEvent) async {
      String msg = '';

      switch (hubEvent.eventName) {
        case 'networkStatus':
          NetworkStatusEvent nse = hubEvent.payload as NetworkStatusEvent;
          msg = 'Amplify hub event - network status is ${nse?.active.toString()}';

          break;
        case 'subscriptionsEstablished':
          msg = 'Amplify hub event - starting to sync from cloud...';
          break;
        case 'syncQueriesStarted':
          msg = 'Amplify hub event - syncing...';
          break;
        case 'modelSynced':
          ModelSyncedEvent mse = hubEvent.payload as ModelSyncedEvent;

          msg = 'Amplify hub event - ${mse.modelName} has been sync\'d from cloud, ${mse.added}, ${mse.updated} ...';
          break;
        case 'syncQueriesReady':
          msg = 'Amplify hub event - sync is done';
          break;
        case 'ready':
          msg = 'Amplify hub event - datastore is ready';

          break;
        case 'outboxMutationEnqueued':
          msg = 'Amplify hub event - local change has been newly staged';
          break;
        case 'outboxMutationProcessed':
          OutboxMutationEvent ome = hubEvent.payload as OutboxMutationEvent;

          msg = 'Amplify hub event - outboxMutationProcessed local change has finished synchronization with the Cloud';
          break;
        case 'outboxStatus':
          OutboxStatusEvent ose = hubEvent.payload as OutboxStatusEvent;
          msg = 'Amplify hub event - outboxStatus local change has finished synchronization with the Cloud';
          break;
        default:
          break;
      }
      if (msg != '') print(msg);
    });
    subscription = Amplify.Hub.listen(HubChannel.Auth, (AuthHubEvent event) {
      print('inside subscription');
      switch (event.type) {
        case AuthHubEventType.signedIn:
          safePrint('User is signed in.');
          Amplify.DataStore.query(Tag.classType).then((s) {
            print('s.length ${s.length}');
          });
          break;
        case AuthHubEventType.signedOut:
          safePrint('User is signed out.');
          break;
        case AuthHubEventType.sessionExpired:
          safePrint('The session has expired.');
          break;
        case AuthHubEventType.userDeleted:
          safePrint('The user has been deleted.');
          break;
      }
    });
  }

  Future<void> _initializeApp() async {
    await _configureAmplify();
  }

  Future<void> _configureAmplify() async {
    if (!Amplify.isConfigured) {
      print('_configureAmplify() configuring amplify');
      try {
        AmplifyDataStore datastorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance);
        final storage = AmplifyStorageS3();
        final api = AmplifyAPI();
        final auth = AmplifyAuthCognito();

        await Amplify.addPlugins([
          auth,
          datastorePlugin,
          storage,
          api,
        ]);
        await Amplify.configure(amplifyconfig);
      } catch (e) {
        print('An error occurred while configuring Amplify: $e');
      }
    }

    await observeEvents();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print('didChangeDependencies - setting subscription');
    _initializeApp();
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: Authenticator.builder(),
      home: Scaffold(
        body: Center(
          child: TextButton(
            onPressed: () {
              Amplify.DataStore.clear().then((s) {
                print('datastore cleared');
                Amplify.DataStore.stop().then((e) {
                  print('datastore stopped');
                  Amplify.Auth.signOut();
                });
              });
            },
            child: Text('Loggout'),
          ),
        ),
      ),
    );
  }
}

Steps to reproduce:

  1. Delete app and reinstall app
  2. Sign in, sign in event runs a query that triggers sync
  3. Make sure all models are sync'ed and sync completes
  4. Log out
  5. Log in again, sign in event runs and the same query triggers sync
  6. Get error
Jordan-Nelson commented 1 month ago

@stephenjen - Thanks for the info. We will try to reproduce with the new info.

stephenjen commented 1 month ago

Opps, I just saw this for Flutter Amplify V2:

image

Am I somehow using 6 subscriptions, so 6 x 17 = 102 which is over the 100 subscriptions per connection limit? Will look into how these are calculated and how they can be increased.

UPDATE 1 Create, update, delete counts as three mutations, and the subscription per connection is an account-level quota. Perhaps I'm hitting the limit because I have other apps on the account taking up the additional subscriptions or model quotas, I'll try to delete all other apps to see if it does anything

UPDATE 2 I just deleted all other backends and all other apps for my account, and still cannot increase the number of models beyond 16. I've requested a quota increase for subscriptions per connection. If and when it is approved I'll report back.

UPDATE 3 Not sure if the number of AppSync subscriptions is the cause of this problem. I have the following in build/schema.graphql:

type Subscription {
  onCreateTag(filter: ModelSubscriptionTagFilterInput): Tag @aws_subscribe(mutations: ["createTag"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag(filter: ModelSubscriptionTagFilterInput): Tag @aws_subscribe(mutations: ["updateTag"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag(filter: ModelSubscriptionTagFilterInput): Tag @aws_subscribe(mutations: ["deleteTag"]) @aws_iam @aws_cognito_user_pools
  onCreateTag1(filter: ModelSubscriptionTag1FilterInput): Tag1 @aws_subscribe(mutations: ["createTag1"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag1(filter: ModelSubscriptionTag1FilterInput): Tag1 @aws_subscribe(mutations: ["updateTag1"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag1(filter: ModelSubscriptionTag1FilterInput): Tag1 @aws_subscribe(mutations: ["deleteTag1"]) @aws_iam @aws_cognito_user_pools
  onCreateTag2(filter: ModelSubscriptionTag2FilterInput): Tag2 @aws_subscribe(mutations: ["createTag2"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag2(filter: ModelSubscriptionTag2FilterInput): Tag2 @aws_subscribe(mutations: ["updateTag2"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag2(filter: ModelSubscriptionTag2FilterInput): Tag2 @aws_subscribe(mutations: ["deleteTag2"]) @aws_iam @aws_cognito_user_pools
  onCreateTag3(filter: ModelSubscriptionTag3FilterInput): Tag3 @aws_subscribe(mutations: ["createTag3"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag3(filter: ModelSubscriptionTag3FilterInput): Tag3 @aws_subscribe(mutations: ["updateTag3"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag3(filter: ModelSubscriptionTag3FilterInput): Tag3 @aws_subscribe(mutations: ["deleteTag3"]) @aws_iam @aws_cognito_user_pools
  onCreateTag4(filter: ModelSubscriptionTag4FilterInput): Tag4 @aws_subscribe(mutations: ["createTag4"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag4(filter: ModelSubscriptionTag4FilterInput): Tag4 @aws_subscribe(mutations: ["updateTag4"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag4(filter: ModelSubscriptionTag4FilterInput): Tag4 @aws_subscribe(mutations: ["deleteTag4"]) @aws_iam @aws_cognito_user_pools
  onCreateTag5(filter: ModelSubscriptionTag5FilterInput): Tag5 @aws_subscribe(mutations: ["createTag5"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag5(filter: ModelSubscriptionTag5FilterInput): Tag5 @aws_subscribe(mutations: ["updateTag5"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag5(filter: ModelSubscriptionTag5FilterInput): Tag5 @aws_subscribe(mutations: ["deleteTag5"]) @aws_iam @aws_cognito_user_pools
  onCreateTag6(filter: ModelSubscriptionTag6FilterInput): Tag6 @aws_subscribe(mutations: ["createTag6"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag6(filter: ModelSubscriptionTag6FilterInput): Tag6 @aws_subscribe(mutations: ["updateTag6"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag6(filter: ModelSubscriptionTag6FilterInput): Tag6 @aws_subscribe(mutations: ["deleteTag6"]) @aws_iam @aws_cognito_user_pools
  onCreateTag7(filter: ModelSubscriptionTag7FilterInput): Tag7 @aws_subscribe(mutations: ["createTag7"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag7(filter: ModelSubscriptionTag7FilterInput): Tag7 @aws_subscribe(mutations: ["updateTag7"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag7(filter: ModelSubscriptionTag7FilterInput): Tag7 @aws_subscribe(mutations: ["deleteTag7"]) @aws_iam @aws_cognito_user_pools
  onCreateTag8(filter: ModelSubscriptionTag8FilterInput): Tag8 @aws_subscribe(mutations: ["createTag8"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag8(filter: ModelSubscriptionTag8FilterInput): Tag8 @aws_subscribe(mutations: ["updateTag8"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag8(filter: ModelSubscriptionTag8FilterInput): Tag8 @aws_subscribe(mutations: ["deleteTag8"]) @aws_iam @aws_cognito_user_pools
  onCreateTag9(filter: ModelSubscriptionTag9FilterInput): Tag9 @aws_subscribe(mutations: ["createTag9"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag9(filter: ModelSubscriptionTag9FilterInput): Tag9 @aws_subscribe(mutations: ["updateTag9"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag9(filter: ModelSubscriptionTag9FilterInput): Tag9 @aws_subscribe(mutations: ["deleteTag9"]) @aws_iam @aws_cognito_user_pools
  onCreateTag10(filter: ModelSubscriptionTag10FilterInput): Tag10 @aws_subscribe(mutations: ["createTag10"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag10(filter: ModelSubscriptionTag10FilterInput): Tag10 @aws_subscribe(mutations: ["updateTag10"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag10(filter: ModelSubscriptionTag10FilterInput): Tag10 @aws_subscribe(mutations: ["deleteTag10"]) @aws_iam @aws_cognito_user_pools
  onCreateTag11(filter: ModelSubscriptionTag11FilterInput): Tag11 @aws_subscribe(mutations: ["createTag11"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag11(filter: ModelSubscriptionTag11FilterInput): Tag11 @aws_subscribe(mutations: ["updateTag11"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag11(filter: ModelSubscriptionTag11FilterInput): Tag11 @aws_subscribe(mutations: ["deleteTag11"]) @aws_iam @aws_cognito_user_pools
  onCreateTag12(filter: ModelSubscriptionTag12FilterInput): Tag12 @aws_subscribe(mutations: ["createTag12"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag12(filter: ModelSubscriptionTag12FilterInput): Tag12 @aws_subscribe(mutations: ["updateTag12"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag12(filter: ModelSubscriptionTag12FilterInput): Tag12 @aws_subscribe(mutations: ["deleteTag12"]) @aws_iam @aws_cognito_user_pools
  onCreateTag13(filter: ModelSubscriptionTag13FilterInput): Tag13 @aws_subscribe(mutations: ["createTag13"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag13(filter: ModelSubscriptionTag13FilterInput): Tag13 @aws_subscribe(mutations: ["updateTag13"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag13(filter: ModelSubscriptionTag13FilterInput): Tag13 @aws_subscribe(mutations: ["deleteTag13"]) @aws_iam @aws_cognito_user_pools
  onCreateTag14(filter: ModelSubscriptionTag14FilterInput): Tag14 @aws_subscribe(mutations: ["createTag14"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag14(filter: ModelSubscriptionTag14FilterInput): Tag14 @aws_subscribe(mutations: ["updateTag14"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag14(filter: ModelSubscriptionTag14FilterInput): Tag14 @aws_subscribe(mutations: ["deleteTag14"]) @aws_iam @aws_cognito_user_pools
  onCreateTag15(filter: ModelSubscriptionTag15FilterInput): Tag15 @aws_subscribe(mutations: ["createTag15"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag15(filter: ModelSubscriptionTag15FilterInput): Tag15 @aws_subscribe(mutations: ["updateTag15"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag15(filter: ModelSubscriptionTag15FilterInput): Tag15 @aws_subscribe(mutations: ["deleteTag15"]) @aws_iam @aws_cognito_user_pools
  onCreateTag16(filter: ModelSubscriptionTag16FilterInput): Tag16 @aws_subscribe(mutations: ["createTag16"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag16(filter: ModelSubscriptionTag16FilterInput): Tag16 @aws_subscribe(mutations: ["updateTag16"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag16(filter: ModelSubscriptionTag16FilterInput): Tag16 @aws_subscribe(mutations: ["deleteTag16"]) @aws_iam @aws_cognito_user_pools
  onCreateTag17(filter: ModelSubscriptionTag17FilterInput): Tag17 @aws_subscribe(mutations: ["createTag17"]) @aws_iam @aws_cognito_user_pools
  onUpdateTag17(filter: ModelSubscriptionTag17FilterInput): Tag17 @aws_subscribe(mutations: ["updateTag17"]) @aws_iam @aws_cognito_user_pools
  onDeleteTag17(filter: ModelSubscriptionTag17FilterInput): Tag17 @aws_subscribe(mutations: ["deleteTag17"]) @aws_iam @aws_cognito_user_pools
}

Which is only 54 subscriptions, under the 100 limit. In working with the team that is in charge of approving changes to quotas, they also asked for the number of realtime updates my app has, which I'm not sure if it is DataStore.observeQuery, which I have two in the app.

harrynguyen2510 commented 1 month ago

@Jordan-Nelson yes @stephenjen I only used 13 model (39 subscriptions) in total but still got WebSocketBloc issue

Jordan-Nelson commented 1 month ago

Thanks for the additional info. We can look into the number of subscriptions. Even though you should be will below the 100 limit, if subscriptions are not getting cleaned up (for example, after stop/start) than it is possible that the 100 limit is being exceeded.

stephenjen commented 1 month ago

@Jordan-Nelson That's a great point. I'm only getting this error the second time sync is initiated - no error for sync after app install and first log in, but error for sync after log out and log back in.

stephenjen commented 1 month ago

@Jordan-Nelson I changed the number of models to 10, and after the 3rd login-sync-logout cycle, on the 4th cycle I get the errors.

stephenjen commented 1 month ago

I downgraded Amplify Flutter from 2.3.0 to 2.2.0, 2.1.0 and finally to 2.0.0. The error goes away at 2.0.0:

amplify_flutter: 2.0.0 amplify_datastore: ^2.0.0 amplify_auth_cognito: ^2.0.0 amplify_authenticator: ^2.0.0 amplify_api: ^2.0.0 amplify_storage_s3: ^2.0.0

@harrynguyen2510 give this a try, see if it works for you.

Equartey commented 4 weeks ago

Hi @stephenjen, thank you for your patience.

We have been able to reproduce this issue and produce a solution. We will notify you when its available.

NikaHsn commented 2 weeks ago

I'm going to close this issue as the fix has been released in 2.4.0 version. If you see otherwise or have additional questions, please open a new issue.

jamontesg commented 2 weeks ago

@NikaHsn / @Equartey

I continuos with the same error.

flutter: ERROR | WebSocketBloc - fb75ea94-3f65-45d9-b762-0e4fed1ae8aa | Shutting down with exception: NetworkException { "message": "Exception from WebSocketService.", "underlyingException": "type '_Map<String, dynamic>' is not a subtype of type 'List?' in type cast" } [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: Cannot add new events after calling close

amplify_analytics_pinpoint: 2.4.0 amplify_api: 2.4.0 amplify_api_dart: 0.5.4 amplify_auth_cognito: 2.4.0 amplify_authenticator: 2.1.2

amplify_core: 2.4.0 amplify_datastore: 2.4.0 amplify_datastore_plugin_interface: 2.4.0 amplify_flutter: 2.4.0 amplify_storage_s3: 2.4.0

tested on IOS

I made a flutter clean and error persist.

stephenjen commented 2 weeks ago

@jamontesg I had to downgrade to 2.0.0 in order for this error to go away. Otherwise I think you'd need to wait for this fix to be merged into next update.

harrynguyen2510 commented 2 weeks ago

Hey, 2.0.0 works for me I talked to support from amplify Main reason is > 2.0.0 versions work well if you're app is Gen 2. If you're app still gen q1. It's still getting bug even latest version released Because gen2 doesn't use datastore, they will show how you merge gen 1 to 2 in advance

Equartey commented 2 weeks ago

@jamontesg sorry to hear you're still having issues. How many models do you have?

@stephenjen @harrynguyen2510 This issue should be resolved in 2.4.0. Can yall verify the error is resolved?

jamontesg commented 2 weeks ago

hi @Equartey I have 18 models issue is present only on IOS, android works without problems (tests on emulators)

Equartey commented 1 week ago

Hi @jamontesg, I improved the error handling of the web socket so we can see the underlying error message. I'm curious to see what the error message after this change. Can you try your workflow again on the latest amplify version, but add this override?

dependency_overrides:
  amplify_api_dart:
    git:
      url: https://github.com/aws-amplify/amplify-flutter.git
      ref: 995e6bdf2c0897dc2f0545c3c3bd347dc2619d80
      path: packages/api/amplify_api_dart
jamontesg commented 1 week ago

hi @Equartey this is the error:

flutter: User is signed in. [log] -->0 flutter: ERROR | WsSubscriptionBloc | { "errors": [ { "errorType": "MaxSubscriptionsReachedError", "message": "Max number of 100 subscriptions reached" } ] } flutter: ERROR | WsSubscriptionBloc | { "errors": [ { "errorType": "MaxSubscriptionsReachedError", "message": "Max number of 100 subscriptions reached" } ] } flutter: ERROR | WsSubscriptionBloc | { "errors": [ { "errorType": "MaxSubscriptionsReachedError", "message": "Max number of 100 subscriptions reached" } ] } flutter: ERROR | WsSubscriptionBloc | { "errors": [ { "errorType": "MaxSubscriptionsReachedError", "message": "Max number of 100 subscriptions reached" } ] } flutter: ERROR | WsSubscriptionBloc | { "errors": [ { "errorType": "MaxSubscriptionsReachedError", "message": "Max number of 100 subscriptions reached" } ] } flutter: ERROR | WsSubscriptionBloc | { "errors": [ { "errorType": "MaxSubscriptionsReachedError", "message": "Max number of 100 subscriptions reached" } ] } flutter: ERROR | WsSubscriptionBloc | { "errors": [ { "errorType": "MaxSubscriptionsReachedError", "message": "Max number of 100 subscriptions reached" } ] } flutter: ERROR | WsSubscriptionBloc | { "errors": [ { "errorType": "MaxSubscriptionsReachedError", "message": "Max number of 100 subscriptions reached" } ] }

Equartey commented 1 week ago

@jamontesg Thank you.

I suspected that was the error. This would be a separate issue from the original one posted here.

I'm going to need more info in order to understand why its not working on iOS, but is on Android. Can you please open a new issue regarding this? Be sure to include your schema and repro steps.

jamontesg commented 1 week ago

Thanks @Equartey issue #5383

stephenjen commented 14 hours ago

@Equartey

I'm on 2.4.1 and get this error after log on and log off (which starts and stop datastore for my app) the 3rd time

flutter: ERROR | WsSubscriptionBloc | {
  "errors": [
    {
      "errorType": "UnknownOperationError",
      "message": "Unknown operation id c5d52a2f-f184-4cbe-8599-2554e2ff2eca"
    }
  ]
}
flutter: ERROR | WsSubscriptionBloc | {
  "errors": [
    {
      "errorType": "UnknownOperationError",
      "message": "Unknown operation id fdcb1842-63cb-4da3-ad81-77bf82262186"
    }
  ]
}
flutter: ERROR | WsSubscriptionBloc | {
  "errors": [
    {
      "errorType": "UnknownOperationError",
      "message": "Unknown operation id de2f07d8-730b-4a5b-b2f7-ba0a794847fc"
    }
  ]
}
...
...
...
flutter: ERROR | WsSubscriptionBloc | {
  "errors": [
    {
      "errorType": "MaxSubscriptionsReachedError",
      "message": "Max number of 200 subscriptions reached"
    }
  ]
}
flutter: ERROR | WsSubscriptionBloc | {
  "errors": [
    {
      "errorType": "MaxSubscriptionsReachedError",
      "message": "Max number of 200 subscriptions reached"
    }
  ]
}
flutter: ERROR | WsSubscriptionBloc | {
  "errors": [
    {
      "errorType": "MaxSubscriptionsReachedError",
      "message": "Max number of 200 subscriptions reached"
    }
  ]
}