Shortly after, for a new to-do, modify, delete function has been completed, but the data are kept in memory, the restart the application data reset, to store data can be deposited into the cell phone store or save to a remote server, this article on how to use the dio will data saved to the server

The source address

The development of preparation

Add dio dependency to pubspec.yaml; A service for storing data. I use JsonBox

dependencies:
  dio: ^ 3.0.1
Copy the code

Configuration dio

Since the application has only one service address, it’s good to create a dio singleton to make the request, create a new request.dart file to configure dio, and use a function to return the created dio instance

  • Set the underlying request address
  • The request timeout period is set
  • Set the debug console to output the request response body to facilitate viewing requests

Basic Settings will work, other Settings can be seen in the Dio documentation

import 'package:dio/dio.dart';

const DB_URL = 'https://jsonbox.io/box_7ea9df49e805cf99509b';

Dio craeteDio() {
  BaseOptions options = BaseOptions(
    baseUrl: DB_URL,
    connectTimeout: 5000,
    receiveTimeout: 3000,); Dio dio = Dio(options); dio.interceptors.add(LogInterceptor( error:true,
    request: false,
    responseBody: true,
    responseHeader: false,
    requestHeader: false));return dio;
}
Copy the code

Modify the Todo model

As toDO data needs to be obtained from the server, the data returned by the service is in JSON format, so we need to convert the JSON data of a single TODO into a TODO instance and create a new model/ Todo.dart file. There are only two more methods than before. The fromJson factory function instantiates a Todo using JSON data. The toJson method converts a Todo into a Map

If a model has fewer fields it can be written by hand, but if it has more fields and is more complex you need to use a tool to help generate code. I used quickType

class Todo {
  String id;
  bool finish;
  String thing;

  Todo({
    this.id,
    this.thing,
    this.finish,
  });

  factory Todo.fromJson(Map<String.dynamic> json) => Todo(
        id: json["_id"].toString(),
        thing: json["thing"],
        finish: json["finish"]);Map<String.dynamic> toJson() => {
        "id": id,
        "thing": thing,
        "finish": finish,
      };
}
Copy the code

Send the request

Dart, store/todos.dart, add a _dio attribute to the todos class to send the request, and a getTodos method to retrieve all toDO list data. Then modify the addTodo, removeTodo, editTodo methods to use _dio to send post, delelte, put requests to the server.

One thing to be aware of is the problem of converting JSON to an instance, which could easily happen

type 'List<dynamic>' is not a subtype of type 'List<Todo>'
Copy the code

This kind of error, this kind of conversion problem, came to me with a little parsing-complex-json-in-flutter after READING an article

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';

import '.. /request.dart';
import '.. /model/todo.dart';

class Todos extends ChangeNotifier {
  List<Todo> _items = [];

  Dio _dio = craeteDio();

  get items {
    return [..._items];
  }

  void refresh() {
    notifyListeners();
  }

  Future<List> getTodos() async {
    try {
      Response response = await _dio.get('/todos');

      final list = response.data as List;
      _items = List<Todo>.from(list.map((i) => Todo.fromJson(i)).toList());

      return items;
    } on DioError catch (err) {
      throw err;
    }
  }

  Future addTodo(String thing) async {
    try {
      Response response = await _dio.post('/todos', data: {
        "thing": thing,
        "finish": false}); Todo todo = Todo( thing: thing, id: response.data["_id"],
        finish: response.data["finish"]); _items.insert(0, todo);
      refresh();
    } on DioError catch (err) {
      throw err;
    }
  }

  Future removeTodo(int index) async {
    try {
      String todoId = _items[index].id;
      await _dio.delete("/todos/$todoId");
      _items.removeAt(index);
      refresh();
    } catch (err) {
      throw err;
    }
  }

  Future editTodo(int index, String thing, bool finish) async {
    String todoId = _items[index].id;

    try {
      await _dio.put("/todos/$todoId", data: {
        "thing": thing,
        "finish": finish,
      });

      Todo todo = _items[index];
      todo.thing = thing;
      todo.finish = finish;
      refresh();
    } catch (e) {
      throwe; }}void toggleFinish(int index) {
    finaltodo = _items[index]; todo.finish = ! todo.finish; refresh(); }bool isTodoExist(String thing) {
    bool isExist = false;

    for (var i = 0; i < _items.length; i++) {
      final todo = _items[i];
      if (todo.thing == thing) {
        isExist = true; }}returnisExist; }}Copy the code

Using the data

Once you have the data, you can use it in the list page. Since the data is returned from the server, there will be time for the request, so you need to use FutureBuilder to render the list. FutureBuilder needs to set a future to determine the state. The Future object returned by the getTodos method of the Todos class is the Future object, and the Builder is a function that takes two parameters, a Context object and a snapshot object, to represent the state of the Future.

The builder method uses a switch statement to determine the future’s state, and returns the parts to render according to the state. None, Active, waiting, done, if none match, Returns a null value.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'store/todos.dart';
import 'widget/add_todo_button.dart';
import 'widget/edit_todo_button.dart';
import 'widget/remove_todo_button.dart';

class TodosPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Flutter Provider Todos')),
      body: FutureBuilder(
        future: Provider.of<Todos>(context).getTodos(),
        builder: (context, snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.none:
              return Text('Press button to start.');
            case ConnectionState.active:
            case ConnectionState.waiting:
              return Center(child: CircularProgressIndicator());
            case ConnectionState.done:
              if (snapshot.hasError) {
                print(snapshot.error);
                return Center(
                  child: Text(
                    'Error, please try again',
                    style: TextStyle(fontSize: 18.0, color: Colors.red),
                  ),
                );
              }

              List items = snapshot.data;

              if (items == null) {
                return Center(
                  child: Text(
                    'Don't have a list yet. Go ahead and add it.',
                    style: TextStyle(fontSize: 18.0),),); }return ListView.builder(
                  itemCount: items.length,
                  itemBuilder: (_, index) {
                    return Column(
                      children: <Widget>[
                        ListTile(
                          title: Text(
                            items[index].thing,
                            style: TextStyle(
                              color: items[index].finish
                                  ? Colors.green
                                  : Colors.grey,
                            ),
                          ),
                          trailing: Container(
                            width: 150,
                            child: Row(
                              mainAxisAlignment: MainAxisAlignment.end,
                              children: <Widget>[
                                EditTodoButton(todoIndex: index),
                                RemoveTodoButton(todoIndex: index),
                              ],
                            ),
                          ),
                        ),
                        Divider(),
                      ],
                    );
                  });
          }
          return null;
        },
      ),
      floatingActionButton: Consumer<Todos>(
        builder: (_, todos, child) {
          returnAddTodoButton(); }, ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ); }}Copy the code

The modify button

In the same way, we need to communicate with the server, so we need to process the logic according to the status of the request. The main modification is to use async/await syntax to wait for the completion of a request, and then process it according to the return value.

Add the Todo button

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '.. /store/todos.dart';

class AddTodoButton extends StatefulWidget {
  @override
  _AddTodoButtonState createState() => _AddTodoButtonState();
}

class _AddTodoButtonState extends State<AddTodoButton> {
  final _formKey = GlobalKey<FormState>();
  final _controller = TextEditingController();

  @override
  void dispose() {
    _formKey.currentState.dispose();
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<Todos>(
      builder: (_, todos, child) {
        _addTodo() async {
          final isValid = _formKey.currentState.validate();

          if(! isValid) {return;
          }

          final thing = _controller.value.text;

          try {
            await todos.addTodo(thing);
            Navigator.pop(context);
            _controller.clear();
          } catch (e) {
            Scaffold.of(context).showSnackBar(
              SnackBar(content: Text('Adding agent failed, please try again. '))); }}return FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {
            print('add todo');
            return showDialog(
              context: context,
              builder: (BuildContext _) {
                return SimpleDialog(
                  title: Text('add Todo),
                  contentPadding: const EdgeInsets.all(24.0),
                  children: <Widget>[
                    Form(
                      key: _formKey,
                      child: Column(
                        children: <Widget>[
                          TextFormField(
                            autofocus: true,
                            autovalidate: false,
                            controller: _controller,
                            keyboardType: TextInputType.text,
                            decoration: InputDecoration(
                              border: OutlineInputBorder(),
                              labelText: 'Type in what you want to do',
                            ),
                            validator: (val) {
                              if (val.isEmpty) {
                                return 'What you want to do can't be idle.';
                              }

                              bool isExist = todos.isTodoExist(val);

                              if (isExist) {
                                return 'This thing already exists.';
                              }
                              return null;
                            },
                          ),
                          SizedBox(height: 20),
                          Row(
                            mainAxisAlignment: MainAxisAlignment.end,
                            children: <Widget>[
                              FlatButton(
                                child: Text('cancel'),
                                onPressed: () {
                                  Navigator.pop(context);
                                },
                              ),
                              RaisedButton(
                                child: Text(
                                  'sure', style: TextStyle(color: Colors.white), ), color: Theme.of(context).primaryColor, onPressed: _addTodo, ), ], ), ], ), ), ], ); }); }); }); }}Copy the code

Edit the Todo button

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '.. /model/todo.dart';
import '.. /store/todos.dart';

class EditTodoButton extends StatefulWidget {
  final todoIndex;

  const EditTodoButton({Key key, this.todoIndex}) : super(key: key);

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

class _EditTodoButtonState extends State<EditTodoButton> {
  final _formKey = GlobalKey<FormState>();

  @override
  voiddispose() { _formKey? .currentState? .dispose();super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<Todos>(
      builder: (context, todos, child) {
        final todoIndex = widget.todoIndex;
        final Todo todo = todos.items[todoIndex];

        return IconButton(
          color: Colors.blue,
          icon: Icon(Icons.edit),
          onPressed: () {
            return showDialog(
              context: context,
              builder: (_) {
                return SimpleDialog(
                  title: Text('edit Todo),
                  contentPadding: const EdgeInsets.all(24.0),
                  children: <Widget>[
                    Form(
                      key: _formKey,
                      child: Column(
                        children: <Widget>[
                          TextFormField(
                            autofocus: false,
                            autovalidate: false,
                            initialValue: todo.thing,
                            decoration: InputDecoration(
                              border: OutlineInputBorder(),
                              labelText: 'Type in what you want to do',
                            ),
                            onChanged: (val) {
                              todo.thing = val;
                            },
                            validator: (val) {
                              if (val.isEmpty) {
                                return 'What you want to do can't be idle.';
                              }
                              return null;
                            },
                          ),
                          SizedBox(height: 20),
                          SwitchListTile(
                            title: const Text('Done or not'),
                            value: todo.finish,
                            onChanged: (bool value) {
                              todo.finish = value;
                            },
                          ),
                          SizedBox(height: 20),
                          Row(
                            mainAxisAlignment: MainAxisAlignment.end,
                            children: <Widget>[
                              FlatButton(
                                child: Text('cancel'),
                                onPressed: () => Navigator.pop(context),
                              ),
                              RaisedButton(
                                child: Text(
                                  'sure',
                                  style: TextStyle(color: Colors.white),
                                ),
                                color: Theme.of(context).primaryColor,
                                onPressed: () async {
                                  final isValid =
                                      _formKey.currentState.validate();

                                  if(! isValid) {return;
                                  }

                                  try {
                                    await todos.editTodo(
                                      todoIndex,
                                      todo.thing,
                                      todo.finish,
                                    );
                                    Navigator.pop(context);
                                  } catch (e) {
                                    Scaffold.of(context).showSnackBar(
                                      SnackBar(content: Text(Modify agent failed, please try again. '))); }},) [,], [, [,], [,], [, }); }); }); }}Copy the code

Remove the Todo button

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '.. /model/todo.dart';
import '.. /store/todos.dart';

class RemoveTodoButton extends StatelessWidget {
  final int todoIndex;

  const RemoveTodoButton({Key key, this.todoIndex}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Consumer<Todos>(builder: (_, todos, child) {
      final Todo todo = todos.items[todoIndex];

      return IconButton(
        color: Colors.red,
        icon: Icon(Icons.delete),
        onPressed: () {
          print('delete todo');
          showDialog(
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: Text('Confirm deletion${todo.thing}? '),
                actions: <Widget>[
                  FlatButton(
                    child: Text(
                      'cancel',
                      style: TextStyle(color: Colors.grey),
                    ),
                    onPressed: () => Navigator.pop(context),
                  ),
                  FlatButton(
                    child: Text('confirm'),
                    onPressed: () async {
                      awaittodos.removeTodo(todoIndex); Navigator.pop(context); },),,); }); }); }); }}Copy the code

conclusion

At this point, all data is stored on the server, and restarting the application data is also retrieved from the server.

The original address

Managing the Flutter Application State using A Provider