A Flutter Todo App using AWS Amplify & GetX
Tags: flutter, aws, codenewbie, github
Published: 2021-05-24 04:35:49

AWSAmplify DataStore enables the developers to add offline capabilities to their apps with just a few lines of code. It provides an on-device data storage service to read, write and observe data (online & offline mode) and sync it to a cloud database (DynamoDB). 

Amplify use GraphQL for the data modeling, and it creates the models in the native language of your framework (JS, Java, Swift, Dart, etc.). This post will use the DataStore to create a Todo App where the user can create, update & delete tasks. 

{% youtube thrhO2Oxqqc %}

GetX is a micro-framework that combines State Management, Dependency Injection, Route Management, and other valuable utilities (e.g., internationalization, theming, validation, etc.). It is one of the most popular state management packages. 

{% twitter 1389634399751262211 %}


We used the Admin UI to create the backend and enable authentication 

![Screen Shot 2021-05-23 at 7.07.05 PM](https://i.imgur.com/RKN8RBa.png)

Create the Flutter App and run the command below to pull the Amplify backend

```shell

amplify pull --appId <Your_appId> --envName staging

```

Follow the instructions described[here](https://docs.amplify.aws/lib/datastore/getting-started/q/platform/flutter#option-2-use-amplify-cli) to add GraphQL API. Once completed, you will get the following in your App.

![Screen Shot 2021-05-23 at 7.28.03 PM](https://i.imgur.com/BVcEDG8.png)


We will need two models (Users & ToDo), update the file amplify/backend/{api_name}/schema.graphql  as below

```graphql

type Todo @model {
  id: ID!
  name: String!
  createdAt: AWSTimestamp
  updatedAt: AWSTimestamp
  isDone: Boolean
  userId: String
}

type Users @model {
  id: ID!
  username: String
  email: AWSEmail
  isVerified: Boolean
  createdAt: AWSTimestamp
}

```

The schema is ready; run the Amplify CLI command below to generate the models. 

```shell

amplify codegen models

```

The models will be created as below

![Screen Shot 2021-05-23 at 7.28.23 PM](https://i.imgur.com/w83MWX2.png)

We will use three GetxControllers:

**AuthController:** This will handle the authentication operations by integrating with AuthService, which will use Amplify to authenticate the user. Once the user is signed in, we will create a user datastore record using DataStoreService. here we will also determine what page to display to the user based on the auth status

```dart
....

handleAuthChanged(isSignedIn) {
    if (!isSignedIn) {
      Get.offAllNamed("/signin");
    } else {
      Get.offAllNamed("/home");
    }
  }

...


```


Check my previous post to learn more about Amplify Authentication

{% post https://dev.to/offlineprogrammer/authentication-flow-with-flutter-aws-amplify-41fa %}

**UserController:** We will use it to get the user record from the DataStore.

```dart
....

  Future<void> getCurrUser() async {
    AuthUser authUser = await _authService.getCurrentUser();
    currentUser.value = await _datastoreService.getUser(authUser.userId);
    print(currentUser.value);
  }

...


```

**TodoController:** This is for creating\deleting\updating the tasks from the DataStore.

```dart
....

  Future<void> getTodos() async {
    AuthUser _authUser = await _authService.getCurrentUser();
    List<Todo> _list = await _datastoreService.getTodos(_authUser.userId);
    todoList.addAllIf(_list != null, _list);
  }

  Future<void> addTodo() async {
    AuthUser _authUser = await _authService.getCurrentUser();
    Todo todo = new Todo(
        name: todoTitleController.text.toString(),
        isDone: false,
        createdAt: TemporalTimestamp.now(),
        updatedAt: TemporalTimestamp.now(),
        userId: _authUser.userId);
    await _datastoreService.saveTodo(todo);
    todoList.add(todo);
  }

  Future<void> removeTodo(Todo todo) async {
    await _datastoreService.removeTodo(todo);
    todoList.remove(todo);
  }

  Future<void> setToDoDone(Todo todo, bool newValue) async {
    await _datastoreService.setToDoDone(todo, newValue);
    todoList[todoList.indexWhere((element) => element.id == todo.id)] =
        todo.copyWith(isDone: newValue);
  }

...

```

In the HomePage, the user will be able to create and manage the Todo tasks. Here we will use the power of GetX and its reactive state manager to enable the following features:

Displaying the user email 

```dart
....

appBar: AppBar(
          title: GetX<UserController>(
            initState: (_) async {
              await Get.find<UserController>().getCurrUser();
            },
            builder: (_) {
              return Text(controller.user?.email ?? '',
                  style: TextStyle(fontSize: 12));
            },
          ),

...


```

Displaying the todo tasks 

```dart
....

    Obx(
        () => Expanded(
            child: ListView.builder(
                itemCount: _todocontroller.todoList.length,
                itemBuilder: (_, index) {
                    return TodoCard(todo: _todocontroller.todoList[index]);
                },
                ),
            ),
        ),

...

```

We will use a Get.dialog to add a new Todo task

```dart
....

        floatingActionButton: FloatingActionButton(
          onPressed: () {
            Get.dialog(AlertDialog(
              title: Text('Add Todo here'),
              content: TextFormField(
                controller: _todocontroller.todoTitleController,
                decoration: InputDecoration(hintText: "Title"),
              ),
              actions: [
                TextButton(
                  child: Text('OK'),
                  onPressed: () async {
                    if (_todocontroller.todoTitleController.text != "") {
                      await _todocontroller.addTodo();
                      _todocontroller.todoTitleController.clear();
                    }
                    Get.back();
                  },
                ),
              ],
            ));
          },
          child: Icon(Icons.add),
        ),

...

```

We will show a checkbox for each task; the user can tab it to flag it as done. Dismissing the task will delete it from the DataStore.

```dart
....

  @override
  Widget build(BuildContext context) {
    TodoController _todocontroller = Get.find();
    return _buildContent(_todocontroller);
  }

  Dismissible _buildContent(TodoController _todocontroller) {
    return Dismissible(
      direction: DismissDirection.startToEnd,
      onDismissed: (DismissDirection direction) {
        if (direction == DismissDirection.startToEnd) {
          _todocontroller.removeTodo(todo);
        }
      },
      background: Container(
        alignment: Alignment.centerLeft,
        color: Colors.red,
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Icon(
            Icons.delete,
            color: Colors.white,
          ),
        ),
      ),
      key: ValueKey(todo.id),
      child: _buildCard(_todocontroller),
    );
  }

  Card _buildCard(TodoController _todocontroller) {
    return Card(
      margin: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
      child: ListTile(
        leading: Checkbox(
          value: todo.isDone,
          onChanged: (newValue) {
            _todocontroller.setToDoDone(todo, newValue);
          },
        ),
        title: Text(
          todo.name,
          style: TextStyle(
            color: Colors.black,
            fontSize: 15,
            fontWeight: FontWeight.bold,
          ),
        ),
        subtitle: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              DateFormat.yMMMMEEEEd().format(
                  DateTime.fromMillisecondsSinceEpoch(
                      todo.createdAt.toSeconds() * 1000)),
              style: TextStyle(
                color: Colors.grey,
                fontSize: 10,
              ),
            ),
          ],
        ),
      ),
    );
  }

...

```

Check the code on github

{% github offlineprogrammer/amplify_todo  %}

> Follow me on <a href="https://twitter.com/_Mo_Malaka_">Twitter</a> for more tips about #coding, #learning, #technology...etc.

> Check my Apps on <a href="https://bit.ly/3h05gQ7">Google Play</a>

<span>Cover image <a href="https://unsplash.com/@kellysikkema?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Kelly Sikkema</a> on <a href="https://unsplash.com/s/photos/to-do-list?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a> </span>