Как синхронизировать c ресурс или объект модели на разных экранах / виджетах при его обновлении? - PullRequest
0 голосов
/ 12 марта 2020

У меня есть REST API, который позволяет пользователю обновлять модель книги

GET /api/books.json # list of books
PUT /api/books/1.json # update the book with id=1

У меня есть соответствующие экраны для этих действий (экран указателя для списка книг; экран редактирования для редактирования сведений о книге) в моем приложении флаттера. При создании формы редактировать книгу,

  1. Я передаю объект книги в форму редактирования
  2. В форме редактирования я делаю копию объекта книги. Я создаю копию, а не редактирую исходный объект, чтобы гарантировать, что объект не будет изменен в случае сбоя обновления на сервере
  3. Если обновление выполнено успешно, я отображаю сообщение об ошибке.

Однако, когда я go возвращаюсь к представлению указателя, название книги остается прежним (так как этот объект не изменился). Кроме того, я обнаружил, что даже если я делаю изменения в исходном объекте, вместо того, чтобы делать копию, метод build не вызывается, когда я go возвращаюсь. Мне интересно, есть ли шаблон, который я могу использовать для обновления этого объекта в приложении при успешном обновлении.

У меня есть следующие классы

class Book {
  final int id;
  final String title;

  Book(this.id, this.title);

  static Book fromJson(json) {
    return Book(
        json['id'],
        json['title']);
  }

  Map<String, dynamic> toJson() => {
        'title': title
      };

  Future<bool> update() {

    var headers = {
      'Content-Type': 'application/json'
    };
    return http
        .put(
          "$HOST/api/books/${id}.json",
          headers: headers,
          body: jsonEncode(this.toJson()),
        )
        .then((response) => response.statusCode == 200);
  }
}

Вот представление индекса

class BooksIndex extends StatefulWidget {
  static final tag = "books-index";

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

class _BooksIndexState extends State<BooksIndex> {
  final Future<http.Response> _getBooks = http.get("$HOST/api/books.json", headers: headers);

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: _getBooks,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            var response = snapshot.data as http.Response;
            if (response.statusCode == 200) {
              List<dynamic> booksJson = jsonDecode(response.body);
              List<Book> books = booksJson.map((bookJson) {
                return Book.fromJson(bookJson);
              }).toList();
              return _buildMaterialApp(ListView.builder(
                itemCount: books.length,
                itemBuilder: (context, index) {
                  var book = books[index];
                  return ListTile(
                    title: Text(book.title),
                    onTap: () {
                      Navigator.push(context, MaterialPageRoute(
                        builder: (context) => BooksEdit(book: book)
                      ));
                    },
                  );
                },
              ));
            } else {
              return _buildMaterialApp(Text(
                  "An error occured while trying to retrieve the books. Status=${response.statusCode}"));
            }
          } else if (snapshot.hasError) {
            return _buildMaterialApp(Text(
                "Could not load books. Please check your internet connection."));
          } else {
            return _buildMaterialApp(Text("Loading"));
          }
        });
  }

  _buildMaterialApp(widget) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Books"),
        ),
        body: widget,
      ),
    );
  }
}

Вот форма редактирования

class BooksEdit extends StatelessWidget {
  final Book book;

  BooksEdit({Key key, @required this.book}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Edit ${book.title}"),
      ),
      body: Column(
        children: <Widget>[
          BookForm(
            book: book,
          )
        ],
      ),
    );
  }
}

class BookForm extends StatefulWidget {
  Book book;

  BookForm({Key key, @required this.book}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _BookFormState();
  }
}

class _BookFormState extends State<BookForm> {
  TextEditingController _titleField;

  RaisedButton _submitBtn;

  bool isError = false;
  String formMessage = "";

  @override
  Widget build(BuildContext context) {
    _titleField = TextEditingController(text: widget.book.title);

    _submitBtn = RaisedButton(
      child: Text(
        "Update",
        style: Theme
          .of(context)
          .textTheme
          .button,
      ),
      color: Theme
        .of(context)
        .primaryColor,
      onPressed: () {
        var book = Book(
          widget.book.id,
          _titleField.text
        );
        book.update().then((success) {
          if (success) {
            setState(() {
              isError = false;
              formMessage = "Successfully updated";
              widget.book = book;
            });
          } else {
            setState(() {
              isError = true;
              formMessage = "Book could not be updated";
            });
          }
        }, onError: (error) {
          setState(() {
            isError = true;
            formMessage =
            "An unexpected error occured. It has been reported to the administrator.";
          });
        });
      },
    );

    var formMessageColor = isError ? Colors.red : Colors.green;

    return Form(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text(
            formMessage,
            style: TextStyle(color: formMessageColor),
          ),
          TextFormField(
            controller: _titleField,
          ),
          _submitBtn
        ],
      ),
    );
  }
}

Здесь основной файл

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

class MyApp extends StatelessWidget {

  final routes = <String, WidgetBuilder>{
   '/': (context) => BooksIndex(),
  };

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "BooksApp",
      theme: ThemeData(primarySwatch: Colors.green),
      routes: routes,
      initialRoute: '/',
    );
  }
}

ТАКЖЕ , я новичок в Flutter. Поэтому я был бы признателен, если бы я получил какие-либо отзывы о других местах в моем коде, которые я могу улучшить.

1 Ответ

1 голос
/ 12 марта 2020

Вы можете скопировать и вставить полный код ниже
Я использую фиксированную json строку для имитации http, когда вызывается update, только изменение json строка
Вы также можете сослаться на официальный пример https://flutter.dev/docs/cookbook/networking/fetch-data Шаг 1: Вы можете await Navigator.push и сделать setState после await для рефракции sh BooksIndex
Шаг 2: Переместить parse json logi c в getBooks
фрагмент кода

return ListTile(
                  title: Text(book.title),
                  onTap: () async {
                    await Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: (context) => BooksEdit(book: book)));
                    setState(() {});
                  },

Future<List<Book>> httpGetBooks() async {
        print("httpGetBooks");
        var response = http.Response(jsonString, 200);
        if (response.statusCode == 200) {
          print("200");
          List<dynamic> booksJson = jsonDecode(response.body);
          List<Book> books = booksJson.map((bookJson) {
            return Book.fromJson(bookJson);
          }).toList();
          print(books[1].title.toString());
          return books;
        }
      }                   

рабочая демонстрация

enter image description here

полный код

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

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

class MyApp extends StatelessWidget {
  final routes = <String, WidgetBuilder>{
    '/': (context) => BooksIndex(),
  };

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "BooksApp",
      theme: ThemeData(primarySwatch: Colors.green),
      routes: routes,
      initialRoute: '/',
    );
  }
}

class BooksEdit extends StatelessWidget {
  final Book book;

  BooksEdit({Key key, @required this.book}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Edit ${book.title}"),
      ),
      body: Column(
        children: <Widget>[
          BookForm(
            book: book,
          )
        ],
      ),
    );
  }
}

class BookForm extends StatefulWidget {
  Book book;

  BookForm({Key key, @required this.book}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _BookFormState();
  }
}

class _BookFormState extends State<BookForm> {
  TextEditingController _titleField;

  RaisedButton _submitBtn;

  bool isError = false;
  String formMessage = "";

  @override
  Widget build(BuildContext context) {
    _titleField = TextEditingController(text: widget.book.title);

    _submitBtn = RaisedButton(
      child: Text(
        "Update",
        style: Theme.of(context).textTheme.button,
      ),
      color: Theme.of(context).primaryColor,
      onPressed: () {
        var book = Book(widget.book.id, _titleField.text);
        book.update().then((success) {
          if (success) {
            setState(() {
              isError = false;
              formMessage = "Successfully updated";
              widget.book = book;
            });
          } else {
            setState(() {
              isError = true;
              formMessage = "Book could not be updated";
            });
          }
        }, onError: (error) {
          setState(() {
            isError = true;
            formMessage =
                "An unexpected error occured. It has been reported to the administrator.";
          });
        });
      },
    );

    var formMessageColor = isError ? Colors.red : Colors.green;

    return Form(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text(
            formMessage,
            style: TextStyle(color: formMessageColor),
          ),
          TextFormField(
            controller: _titleField,
          ),
          _submitBtn
        ],
      ),
    );
  }
}

class BooksIndex extends StatefulWidget {
  static final tag = "books-index";

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

String jsonString = '''
[{
   "id" : 1,
   "title" : "t"
}
,
{
    "id" : 2,
   "title" : "t1"
}
]
''';

class _BooksIndexState extends State<BooksIndex> {
  Future<List<Book>> httpGetBooks() async {
    print("httpGetBooks");
    var response = http.Response(jsonString, 200);
    if (response.statusCode == 200) {
      print("200");
      List<dynamic> booksJson = jsonDecode(response.body);
      List<Book> books = booksJson.map((bookJson) {
        return Book.fromJson(bookJson);
      }).toList();
      print(books[1].title.toString());
      return books;
    }
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print("build ${jsonString}");
    return FutureBuilder<List<Book>>(
        future: httpGetBooks(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            print("hasData");
            return _buildMaterialApp(ListView.builder(
              itemCount: snapshot.data.length,
              itemBuilder: (context, index) {
                var book = snapshot.data[index];
                print(book.title);
                return ListTile(
                  title: Text(book.title),
                  onTap: () async {
                    await Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: (context) => BooksEdit(book: book)));
                    setState(() {});
                  },
                );
              },
            ));
          } else if (snapshot.hasError) {
            return _buildMaterialApp(Text(
                "Could not load books. Please check your internet connection."));
          } else {
            return _buildMaterialApp(Text("Loading"));
          }
        });
  }

  _buildMaterialApp(widget) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Books"),
        ),
        body: widget,
      ),
    );
  }
}

class Book {
  final int id;
  final String title;

  Book(this.id, this.title);

  static Book fromJson(json) {
    return Book(json['id'], json['title']);
  }

  Map<String, dynamic> toJson() => {'title': title};

  Future<bool> update() {
    print("update");
    var headers = {'Content-Type': 'application/json'};
    /*return http
        .put(
          "$HOST/api/books/${id}.json",
          headers: headers,
          body: jsonEncode(this.toJson()),
        )
        .then((response) => response.statusCode == 200);*/

    jsonString = '''
    [{
   "id" : 1,
   "title" : "t"
}
,
{
    "id" : 2,
   "title" : "test"
}
]
    ''';
    return Future.value(true);
  }
}


                  setState(() {

                  });

                },
              );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...