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.33k stars 248 forks source link

Todos items not syncing to the cloud #2102

Closed Intelbis closed 2 years ago

Intelbis commented 2 years ago

Description

I have added the Amplify auth ui and CRUD Operations using the amplify docs. There are no run time errors in the code. The app loads on the phone and lets me sign in to the TodosPage. It lets me add a new todo and to save it and I can see all the list items but its not syncing to the cloud. The content section of amplify keeps loading. Any work around for this to view the data in amplifyStudio?

Categories

Steps to Reproduce

See reproduction app:

**Main** ```dart // dart async library we will refer to when setting up real time updates import 'dart:async'; // flutter and ui libraries import 'package:flutter/material.dart'; // amplify packages we will need to use import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_api/amplify_api.dart'; import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:ilabs_removal_app/screens/authgate.dart'; // amplify configuration and models that should have been generated for you import 'amplifyconfiguration.dart'; import 'models/ModelProvider.dart'; import 'models/Todo.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Amplified Todo', home: authgate(), ); } } ``` **authgate** ```dart import 'dart:async'; 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_datastore/amplify_datastore.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:flutter/material.dart'; import 'package:ilabs_removal_app/screens/TodosPage.dart'; import '../amplifyconfiguration.dart'; import '../models/ModelProvider.dart'; import '../models/Todo.dart'; class authgate extends StatefulWidget { const authgate({Key? key}) : super(key: key); @override State createState() => _authgateState(); } class _authgateState extends State { @override void initState() { super.initState(); _configureAmplify(); } // void _configureAmplify() async { // try { // await Amplify.addPlugin(AmplifyAuthCognito()); // await Amplify.configure(amplifyconfig); // print('Successfully configured'); // } on Exception catch (e) { // print('Error configuring Amplify: $e'); // } // } final _dataStorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance); final AmplifyAPI _apiPlugin = AmplifyAPI(); // final AmplifyAuthCognito _authPlugin = AmplifyAuthCognito(); Future _configureAmplify() async { try { // add Amplify plugins await Amplify.addPlugins([ _dataStorePlugin, _apiPlugin,]); await Amplify.addPlugin(AmplifyAuthCognito()); // configure Amplify // // note that Amplify cannot be configured more than once! await Amplify.configure(amplifyconfig); } catch (e) { // error handling can be improved for sure! // but this will be sufficient for the purposes of this tutorial print('An error occurred while configuring Amplify: $e'); } // to be filled in a later step } @override Widget build(BuildContext context) { return Authenticator( child: MaterialApp( builder: Authenticator.builder(), home: TodosPage(), ), ); } } ``` **todos page** ```dart import 'dart:async'; import 'package:amplify_api/amplify_api.dart'; import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../amplifyconfiguration.dart'; import '../models/ModelProvider.dart'; import '../models/Todo.dart'; class TodosPage extends StatefulWidget { const TodosPage({Key? key}) : super(key: key); @override State createState() => _TodosPageState(); } class _TodosPageState extends State { late StreamSubscription> _subscription; // loading ui state - initially set to a loading state bool _isLoading = true; // list of Todos - initially empty List _todos = []; // amplify plugins // final _dataStorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance); // final _authPlugin = AmplifyAuthCognito(); // final _apiPlugin = AmplifyAPI(modelProvider: ModelProvider.instance); @override void initState() { // kick off app initialization _initializeApp(); // to be filled in a later step super.initState(); } @override void dispose() { // to be filled in a later step super.dispose(); } Future _initializeApp() async { // configure Amplify // await _configureAmplify(); // after configuring Amplify, update loading ui state to loaded state _subscription = Amplify.DataStore.observeQuery(Todo.classType) .listen((QuerySnapshot snapshot) { setState(() { if (_isLoading) _isLoading = false; _todos = snapshot.items; }); }); // to be filled in a later step } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('My Todo List'), ), // body: const Center(child: CircularProgressIndicator()), body: _isLoading ? Center(child: CircularProgressIndicator()) : TodosList(todos: _todos), floatingActionButton: FloatingActionButton.extended( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const AddTodoForm()), ); }, tooltip: 'Add Todo', label: Row( children: const [Icon(Icons.add), Text('Add todo')], ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ); } } class TodosList extends StatelessWidget { const TodosList({ required this.todos, Key? key, }) : super(key: key); final List todos; @override Widget build(BuildContext context) { return todos.isNotEmpty ? ListView( padding: const EdgeInsets.all(8), children: todos.map((todo) => TodoItem(todo: todo)).toList()) : const Center( child: Text('Tap button below to add a todo!'), ); } } class TodoItem extends StatelessWidget { const TodoItem({ required this.todo, Key? key, }) : super(key: key); final double iconSize = 24.0; final Todo todo; void _deleteTodo(BuildContext context) async { // to be filled in a later step } Future _toggleIsComplete() async { // to be filled in a later step } @override Widget build(BuildContext context) { return Card( child: InkWell( onTap: () { _toggleIsComplete(); }, onLongPress: () { _deleteTodo(context); }, child: Padding( padding: const EdgeInsets.all(8.0), child: Row(children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( todo.name, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold), ), Text(todo.description ?? 'No description'), ], ), ), Icon( todo.isComplete ? Icons.check_box : Icons.check_box_outline_blank, size: iconSize), ]), ), ), ); } } class AddTodoForm extends StatefulWidget { const AddTodoForm({Key? key}) : super(key: key); @override State createState() => _AddTodoFormState(); } class _AddTodoFormState extends State { final _nameController = TextEditingController(); final _descriptionController = TextEditingController(); Future _saveTodo() async { // get the current text field contents final name = _nameController.text; final description = _descriptionController.text; // create a new Todo from the form values // `isComplete` is also required, but should start false in a new Todo final newTodo = Todo( name: name, description: description.isNotEmpty ? description : null, isComplete: false, ); try { // to write data to DataStore, we simply pass an instance of a model to // Amplify.DataStore.save() await Amplify.DataStore.save(newTodo); // after creating a new Todo, close the form Navigator.of(context).pop(); } catch (e) { print('An error occurred while saving Todo: $e'); } // to be filled in a later step } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Add Todo'), ), body: Padding( padding: const EdgeInsets.all(8.0), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextFormField( controller: _nameController, decoration: const InputDecoration(filled: true, labelText: 'Name'), ), TextFormField( controller: _descriptionController, decoration: const InputDecoration( filled: true, labelText: 'Description'), ), ElevatedButton( onPressed: _saveTodo, child: const Text('Save'), ) ], ), ), ), ); } } ```

Screenshots

No response

Platforms

Android Device/Emulator API Level

No response

Environment

seankeish@Seans-MBP ~ % flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.0.5, on macOS 12.5.1 21G83 darwin-arm, locale
    en-AU)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 13.4.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.2)
[✓] VS Code (version 1.71.0)
[✓] Connected device (3 available)
[✓] HTTP Host Availability

• No issues found!

Dependencies

name: ilabs_removal_app
description: A new Flutter project.

# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1

environment:
  sdk: ">=2.15.0 <3.0.0"

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter

  amplify_flutter: ^0.6.0
  amplify_datastore: ^0.6.0
  amplify_api: ^0.6.0
  amplify_auth_cognito: ^0.6.0
  amplify_authenticator: ^0.2.0

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  get: ^4.6.5

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

Device

Pixel 5

OS

Android 12

Deployment Method

Amplify CLI

CLI Version

No response

Additional Context

No response

Amplify Config

"UserAgent": "aws-amplify-cli/2.0",
"Version": "1.0",
ragingsquirrel3 commented 2 years ago

Because you are getting this error in ios do you mind running the app from xcode and seeing if there are any errors in xcode console? If you see any errors do you mind posting them here? Also do you mind posting your amplifyconfiguration.dart file (with any api keys or other values redacted)?

Intelbis commented 2 years ago

Hi i rearrange my code just in the main. How to configure ampliyAuthUI and datastore crud functions in one amplify configuration? We can only configure it once right?

```dart import 'dart:async'; 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_datastore/amplify_datastore.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:flutter/material.dart'; import 'package:ilabs_removal_app/screens/TodosPage.dart'; import 'amplifyconfiguration.dart'; import 'models/ModelProvider.dart'; import 'models/Todo.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { super.initState(); _configureAmplify(); } void _configureAmplify() async { try { await Amplify.addPlugin(AmplifyAuthCognito()); await Amplify.configure(amplifyconfig); print('Successfully configured'); } on Exception catch (e) { print('Error configuring Amplify: $e'); } } @override Widget build(BuildContext context) { return Authenticator( child: MaterialApp( builder: Authenticator.builder(), home: const TodosPage(), ), ); } } class TodosPage extends StatefulWidget { const TodosPage({Key? key}) : super(key: key); @override State createState() => _TodosPageState(); } class _TodosPageState extends State { // subscription of Todo QuerySnapshots - to be initialized at runtime late StreamSubscription> _subscription; // loading ui state - initially set to a loading state bool _isLoading = true; // list of Todos - initially empty List _todos = []; // amplify plugins // final _dataStorePlugin = // AmplifyDataStore(modelProvider: ModelProvider.instance); final _dataStorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance); final AmplifyAPI _apiPlugin = AmplifyAPI(); final AmplifyAuthCognito _authPlugin = AmplifyAuthCognito(); @override void initState() { // kick off app initialization _initializeApp(); // to be filled in a later step super.initState(); } @override void dispose() { // to be filled in a later step super.dispose(); } Future _initializeApp() async { // configure Amplify await _configureAmplify(); // Query and Observe updates to Todo models. DataStore.observeQuery() will // emit an initial QuerySnapshot with a list of Todo models in the local store, // and will emit subsequent snapshots as updates are made // // each time a snapshot is received, the following will happen: // _isLoading is set to false if it is not already false // _todos is set to the value in the latest snapshot _subscription = Amplify.DataStore.observeQuery(Todo.classType) .listen((QuerySnapshot snapshot) { setState(() { if (_isLoading) _isLoading = false; _todos = snapshot.items; }); }); } Future _configureAmplify() async { try { // add Amplify plugins await Amplify.addPlugins([_dataStorePlugin, _apiPlugin, _authPlugin]); // configure Amplify // // note that Amplify cannot be configured more than once! await Amplify.configure(amplifyconfig); } catch (e) { // error handling can be improved for sure! // but this will be sufficient for the purposes of this tutorial print('An error occurred while configuring Amplify: $e'); } // to be filled in a later step } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('My Todo List'), ), body: _isLoading ? Center(child: CircularProgressIndicator()) : TodosList(todos: _todos), floatingActionButton: FloatingActionButton.extended( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const AddTodoForm()), ); }, tooltip: 'Add Todo', label: Row( children: const [Icon(Icons.add), Text('Add todo')], ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ); } } class TodosList extends StatelessWidget { const TodosList({ required this.todos, Key? key, }) : super(key: key); final List todos; @override Widget build(BuildContext context) { return todos.isNotEmpty ? ListView( padding: const EdgeInsets.all(8), children: todos.map((todo) => TodoItem(todo: todo)).toList()) : const Center( child: Text('Tap button below to add a todo!'), ); } } class TodoItem extends StatelessWidget { const TodoItem({ required this.todo, Key? key, }) : super(key: key); final double iconSize = 24.0; final Todo todo; void _deleteTodo(BuildContext context) async { try { // to delete data from DataStore, we pass the model instance to // Amplify.DataStore.delete() await Amplify.DataStore.delete(todo); } catch (e) { print('An error occurred while deleting Todo: $e'); } // to be filled in a later step } Future _toggleIsComplete() async { // copy the Todo we wish to update, but with updated properties final updatedTodo = todo.copyWith(isComplete: !todo.isComplete); try { // to update data in DataStore, we again pass an instance of a model to // Amplify.DataStore.save() await Amplify.DataStore.save(updatedTodo); } catch (e) { print('An error occurred while saving Todo: $e'); } // to be filled in a later step } @override Widget build(BuildContext context) { return Card( child: InkWell( onTap: () { _toggleIsComplete(); }, onLongPress: () { _deleteTodo(context); }, child: Padding( padding: const EdgeInsets.all(8.0), child: Row(children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( todo.name, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold), ), Text(todo.description ?? 'No description'), ], ), ), Icon( todo.isComplete ? Icons.check_box : Icons.check_box_outline_blank, size: iconSize), ]), ), ), ); } } class AddTodoForm extends StatefulWidget { const AddTodoForm({Key? key}) : super(key: key); @override State createState() => _AddTodoFormState(); } class _AddTodoFormState extends State { final _nameController = TextEditingController(); final _descriptionController = TextEditingController(); Future _saveTodo() async { // get the current text field contents final name = _nameController.text; final description = _descriptionController.text; // create a new Todo from the form values // `isComplete` is also required, but should start false in a new Todo final newTodo = Todo( name: name, description: description.isNotEmpty ? description : null, isComplete: false, ); try { // to write data to DataStore, we simply pass an instance of a model to // Amplify.DataStore.save() await Amplify.DataStore.save(newTodo); // after creating a new Todo, close the form Navigator.of(context).pop(); } catch (e) { print('An error occurred while saving Todo: $e'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Add Todo'), ), body: Padding( padding: const EdgeInsets.all(8.0), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextFormField( controller: _nameController, decoration: const InputDecoration(filled: true, labelText: 'Name'), ), TextFormField( controller: _descriptionController, decoration: const InputDecoration( filled: true, labelText: 'Description'), ), ElevatedButton( onPressed: _saveTodo, child: const Text('Save'), ) ], ), ), ), ); } } ```
Intelbis commented 2 years ago
const amplifyconfig = ''' {
    "UserAgent": "aws-amplify-cli/2.0",
    "Version": "1.0",
    "api": {
        "plugins": {
            "awsAPIPlugin": {
                "removalist": {
                    "endpointType": "GraphQL",
                    "endpoint": "end-point",
                    "region": "ap-southeast-2",
                    "authorizationType": "AMAZON_COGNITO_USER_POOLS",
                    "apiKey": "api-key"
                }
            }
        }
    },
    "auth": {
        "plugins": {
            "awsCognitoAuthPlugin": {
                "UserAgent": "aws-amplify-cli/0.1.0",
                "Version": "0.1.0",
                "IdentityManager": {
                    "Default": {}
                },
                "AppSync": {
                    "Default": {
                        "ApiUrl": "https://j2632ce4znfz3gpt5vtgw5qzzm.appsync-api.ap-southeast-2.amazonaws.com/graphql",
                        "Region": "ap-southeast-2",
                        "AuthMode": "AMAZON_COGNITO_USER_POOLS",
                        "ClientDatabasePrefix": "removalist_AMAZON_COGNITO_USER_POOLS"
                    },
                    "removalist_AWS_IAM": {
                        "ApiUrl": "https://j2632ce4znfz3gpt5vtgw5qzzm.appsync-api.ap-southeast-2.amazonaws.com/graphql",
                        "Region": "ap-southeast-2",
                        "AuthMode": "AWS_IAM",
                        "ClientDatabasePrefix": "removalist_AWS_IAM"
                    },
                    "removalist_API_KEY": {
                        "ApiUrl": "https://j2632ce4znfz3gpt5vtgw5qzzm.appsync-api.ap-southeast-2.amazonaws.com/graphql",
                        "Region": "ap-southeast-2",
                        "AuthMode": "API_KEY",
                        "ApiKey": "da2-api-key",
                        "ClientDatabasePrefix": "removalist_API_KEY"
                    }
                },
                "CredentialsProvider": {
                    "CognitoIdentity": {
                        "Default": {
                            "PoolId": "pool-id",
                            "Region": "ap-southeast-2"
                        }
                    }
                },
                "CognitoUserPool": {
                    "Default": {
                        "PoolId": "poolid",
                        "AppClientId": "clientid",
                        "Region": "ap-southeast-2"
                    }
                },
                "Auth": {
                    "Default": {
                        "authenticationFlowType": "USER_SRP_AUTH",
                        "socialProviders": [],
                        "usernameAttributes": [
                            "EMAIL"
                        ],
                        "signupAttributes": [],
                        "passwordProtectionSettings": {
                            "passwordPolicyMinLength": 8,
                            "passwordPolicyCharacters": [
                                "REQUIRES_LOWERCASE",
                                "REQUIRES_NUMBERS",
                                "REQUIRES_SYMBOLS",
                                "REQUIRES_UPPERCASE"
                            ]
                        },
                        "mfaConfiguration": "OFF",
                        "mfaTypes": [
                            "SMS"
                        ],
                        "verificationMechanisms": [
                            "EMAIL"
                        ]
                    }
                }
            }
        }
    }
}''';
Intelbis commented 2 years ago

Because you are getting this error in ios do you mind running the app from xcode and seeing if there are any errors in xcode console? If you see any errors do you mind posting them here? Also do you mind posting your amplifyconfiguration.dart file (with any api keys or other values redacted)?

hey Travis please see the code below for my latest changes in the main and my configuration has been posted in the previous post

```dart import 'dart:async'; 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_datastore/amplify_datastore.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:flutter/material.dart'; import 'package:ilabs_removal_app/screens/TodosPage.dart'; import 'amplifyconfiguration.dart'; import 'models/ModelProvider.dart'; import 'models/Todo.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { super.initState(); _configureAmplify(); } @override Widget build(BuildContext context) { return Authenticator( child: MaterialApp( builder: Authenticator.builder(), home: const TodosPage(), ), ); } } void _configureAmplify() async { final datastorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance,); // Add the following line and update your function call with `addPlugins` final api = AmplifyAPI(); await Amplify.addPlugins([datastorePlugin, api ]); await Amplify.addPlugin(AmplifyAuthCognito()); try { await Amplify.configure(amplifyconfig); } on AmplifyAlreadyConfiguredException { print('Tried to reconfigure Amplify; this can occur when your app restarts on Android.'); } } class TodosPage extends StatefulWidget { const TodosPage({Key? key}) : super(key: key); @override State createState() => _TodosPageState(); } class _TodosPageState extends State { // subscription of Todo QuerySnapshots - to be initialized at runtime late StreamSubscription> _subscription; // loading ui state - initially set to a loading state bool _isLoading = true; // list of Todos - initially empty List _todos = []; // amplify plugins // final _dataStorePlugin = // AmplifyDataStore(modelProvider: ModelProvider.instance); // final _dataStorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance); // final AmplifyAPI _apiPlugin = AmplifyAPI(); // final AmplifyAuthCognito _authPlugin = AmplifyAuthCognito(); @override void initState() { // kick off app initialization _initializeApp(); // to be filled in a later step super.initState(); } @override void dispose() { // to be filled in a later step super.dispose(); } Future _initializeApp() async { // configure Amplify // await _configureAmplify(); // Query and Observe updates to Todo models. DataStore.observeQuery() will // emit an initial QuerySnapshot with a list of Todo models in the local store, // and will emit subsequent snapshots as updates are made // // each time a snapshot is received, the following will happen: // _isLoading is set to false if it is not already false // _todos is set to the value in the latest snapshot _subscription = Amplify.DataStore.observeQuery(Todo.classType) .listen((QuerySnapshot snapshot) { setState(() { if (_isLoading) _isLoading = false; _todos = snapshot.items; }); }); } // // Future _configureAmplify() async { // // try { // // // // add Amplify plugins // // await Amplify.addPlugins([_dataStorePlugin, _apiPlugin]); // // // configure Amplify // // // // note that Amplify cannot be configured more than once! // // await Amplify.configure(amplifyconfig); // } catch (e) { // // // error handling can be improved for sure! // // but this will be sufficient for the purposes of this tutorial // print('An error occurred while configuring Amplify: $e'); // } // // // to be filled in a later step // } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('My Todo List'), ), body: _isLoading ? Center(child: CircularProgressIndicator()) : TodosList(todos: _todos), floatingActionButton: FloatingActionButton.extended( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => const AddTodoForm()), ); }, tooltip: 'Add Todo', label: Row( children: const [Icon(Icons.add), Text('Add todo')], ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ); } } class TodosList extends StatelessWidget { const TodosList({ required this.todos, Key? key, }) : super(key: key); final List todos; @override Widget build(BuildContext context) { return todos.isNotEmpty ? ListView( padding: const EdgeInsets.all(8), children: todos.map((todo) => TodoItem(todo: todo)).toList()) : const Center( child: Text('Tap button below to add a todo!'), ); } } class TodoItem extends StatelessWidget { const TodoItem({ required this.todo, Key? key, }) : super(key: key); final double iconSize = 24.0; final Todo todo; void _deleteTodo(BuildContext context) async { try { // to delete data from DataStore, we pass the model instance to // Amplify.DataStore.delete() await Amplify.DataStore.delete(todo); } catch (e) { print('An error occurred while deleting Todo: $e'); } // to be filled in a later step } Future _toggleIsComplete() async { // copy the Todo we wish to update, but with updated properties final updatedTodo = todo.copyWith(isComplete: !todo.isComplete); try { // to update data in DataStore, we again pass an instance of a model to // Amplify.DataStore.save() await Amplify.DataStore.save(updatedTodo); } catch (e) { print('An error occurred while saving Todo: $e'); } // to be filled in a later step } @override Widget build(BuildContext context) { return Card( child: InkWell( onTap: () { _toggleIsComplete(); }, onLongPress: () { _deleteTodo(context); }, child: Padding( padding: const EdgeInsets.all(8.0), child: Row(children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( todo.name, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold), ), Text(todo.description ?? 'No description'), ], ), ), Icon( todo.isComplete ? Icons.check_box : Icons.check_box_outline_blank, size: iconSize), ]), ), ), ); } } class AddTodoForm extends StatefulWidget { const AddTodoForm({Key? key}) : super(key: key); @override State createState() => _AddTodoFormState(); } class _AddTodoFormState extends State { final _nameController = TextEditingController(); final _descriptionController = TextEditingController(); Future _saveTodo() async { // get the current text field contents final name = _nameController.text; final description = _descriptionController.text; // create a new Todo from the form values // `isComplete` is also required, but should start false in a new Todo final newTodo = Todo( name: name, description: description.isNotEmpty ? description : null, isComplete: false, ); try { // to write data to DataStore, we simply pass an instance of a model to // Amplify.DataStore.save() await Amplify.DataStore.save(newTodo); // after creating a new Todo, close the form Navigator.of(context).pop(); } catch (e) { print('An error occurred while saving Todo: $e'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Add Todo'), ), body: Padding( padding: const EdgeInsets.all(8.0), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextFormField( controller: _nameController, decoration: const InputDecoration(filled: true, labelText: 'Name'), ), TextFormField( controller: _descriptionController, decoration: const InputDecoration( filled: true, labelText: 'Description'), ), ElevatedButton( onPressed: _saveTodo, child: const Text('Save'), ) ], ), ), ), ); } } ```
Intelbis commented 2 years ago

Because you are getting this error in ios do you mind running the app from xcode and seeing if there are any errors in xcode console? If you see any errors do you mind posting them here? Also do you mind posting your amplifyconfiguration.dart file (with any api keys or other values redacted)?

Hi I successfully managed to implement auth and crud ops together. But I am facing another minor issue that I have to close and open the app to see changes in the app when i create a new Todo from the amplify content. Please let me know how to fix this I really appreciate the help thank you!

Jordan-Nelson commented 2 years ago

@Intelbis Can you confirm that the callback for this subscription is not called when a new item is added?

    _subscription = Amplify.DataStore.observeQuery(Todo.classType)
        .listen((QuerySnapshot<Todo> snapshot) {
      setState(() {
        if (_isLoading) _isLoading = false;
        _todos = snapshot.items;
      });

If that is the case, I think @ragingsquirrel3's suggestion is a good next step. If you can run the app from Xcode and include any logs that would be helpful. Thanks.

Intelbis commented 2 years ago

@Intelbis Can you confirm that the callback for this subscription is not called when a new item is added?

    _subscription = Amplify.DataStore.observeQuery(Todo.classType)
        .listen((QuerySnapshot<Todo> snapshot) {
      setState(() {
        if (_isLoading) _isLoading = false;
        _todos = snapshot.items;
      });

If that is the case, I think @ragingsquirrel3's suggestion is a good next step. If you can run the app from Xcode and include any logs that would be helpful. Thanks.

Hi Thanks for the reply Jordan. I was able to solve this issue successfully with the default Todo properties in the Doc but the issue Im facing now is that I tried adding a new property and deployed it. But now the it it says failed to sync on amplify. In the log it shows "Orchestrator transitioning from SYNC_VIA_API to LOCAL_ONLY" "Setting currentState to LOCAL_ONLY" "Stopping subscription processor." "No more active subscriptions. Closing web socket." I have posted this as a new issue here. Thanks for the help :)

Jordan-Nelson commented 2 years ago

Okay, I am glad you were able to resolve this issue. I will close this one out and take a look at the new issue you opened.