Flutter: сохранение состояния страницы даже после перехода между экранами

#firebase #flutter #google-cloud-firestore #navigation #state-management

#firebase #flutter #google-облако-firestore #навигация #управление состоянием

Вопрос:

У меня есть простая задача. В моем приложении есть 2 экрана, на главном экране у меня есть кнопка, которая перенаправляет меня на новую страницу под названием «Интересы». Страница интересов представляет собой список флажков (для которых я должен использовать только listview.builder) и кнопку для отправки данных (которая возвращает меня к главному экрану). Чего я хочу добиться, так это:

  1. флажки должны работать правильно.
  2. когда я перехожу со страницы интересов на главную страницу и снова возвращаюсь на страницу интересов, выбранные флажки должны оставаться отмеченными. Короче говоря, состояние страницы должно быть сохранено.
  3. Я написал функцию «applyInterestChanges» для сохранения данных в базе данных. Я должен получить те же данные, чтобы отобразить выбранные флажки (что мы делали, передавая данные через конструктор).

Любая помощь будет оценена!!

 import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: RaisedButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => Interests(),
                ),
              );
            },
            child: Text("Click here!!"),
          ),
        ),
      ),
    );
  }
}

class Interests extends StatefulWidget {
  final List<dynamic> selectedList;
  final void Function(List<dynamic>) callback;

  Interests(this.selectedList, this.callback);

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

class _InterestsState extends State<Interests> {
  Map<String, dynamic> _categories = {
    "responseCode": "1",
    "responseText": "List categories.",
    "responseBody": [
      {"category_id": "1", "category_name": "Movies"},
      {"category_id": "2", "category_name": "Sports"},
      {"category_id": "3", "category_name": "Food"},
      {"category_id": "4", "category_name": "Music"},
      {"category_id": "5", "category_name": "Others"},
    ],
    "responseTotalResult": 5
  };

  void _onCategorySelected(bool selected, categoryName) {
    if (selected == true) {
      setState(() {
        widget.selectedList.add(categoryName);
      });
    } else {
      setState(() {
        widget.selectedList.remove(categoryName);
      });
    }
    widget.callback(widget.selectedList);
  }

  applyInterestChanges() { //function to save the changes in database.
    Firestore.instance
        .collection('my_users')
        .document(currentUserModel.id)
        .updateData({
      "interests": widget.selectedList,
    });
  } //this code is working properly. Need to similar function to retrieve the data and display the updated interests list.

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Interests"),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            Text(
              "Select your interests: ",
              style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
            ),
            ListView.builder(
              physics: NeverScrollableScrollPhysics(),
              scrollDirection: Axis.vertical,
              shrinkWrap: true,
              itemCount: _categories['responseTotalResult'],
              itemBuilder: (BuildContext context, int index) {
                return CheckboxListTile(
                  controlAffinity: ListTileControlAffinity.leading,
                  value: widget.selectedList.contains(
                      _categories['responseBody'][index]['category_name']),
                  onChanged: (bool selected) {
                    _onCategorySelected(selected,
                        _categories['responseBody'][index]['category_name']);
                  },
                  title:
                      Text(_categories['responseBody'][index]['category_name']),
                );
              },
            ),
            MaterialButton(
              onPressed: () {
                Navigator.pop(context);
                applyInterestChanges();
              },
              child: Text("Submit"),
            ),
          ],
        ),
      ),
    );
  }
}

  

Ответ №1:

Вы можете передать пустой список из родительского виджета MyHomeWidget и обновить этот список с помощью обратного вызова из Interests виджета.

В следующий раз, когда вы вернетесь и снова перейдете к Interests виджету, мы передадим этот обновленный список, который сохранит состояние Interests виджета. Следовательно, флажки будут установлены в зависимости от их значений в списке.

Вот реализация:

 import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatefulWidget {
  
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<dynamic> selectedList = [];
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: RaisedButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => Interests(
                    selectedList,
                    (List<dynamic> updatedList) {
                      setState(() {
                        selectedList = updatedList;
                      });
                    }
                  ),
                ),
              );
            },
            child: Text("Click here!!"),
          ),
        ),
      ),
    );
  }
}

class Interests extends StatefulWidget {
  Interests(this.selectedList, this.callback);
  
  // Passing the list from parent widget i.e, MyHomeWidget
  // Initially the list will be empty
  // We will update the list in parent whenever checkboxes change
  final List<dynamic> selectedList;
  
  // Creating a callback function to save state(update list) in 
  // MyHomeWidget
  final void Function(List<dynamic>) callback;
  
  @override
  _InterestsState createState() => _InterestsState();
}

class _InterestsState extends State<Interests> {
  
  Map<String, dynamic> _categories = {
    "responseCode": "1",
    "responseText": "List categories.",
    "responseBody": [
      {"category_id": "1", "category_name": "Movies"},
      {"category_id": "2", "category_name": "Sports"},
      {"category_id": "3", "category_name": "Food"},
      {"category_id": "4", "category_name": "Music"},
      {"category_id": "5", "category_name": "Others"},
    ],
    "responseTotalResult": 5
  };

  void _onCategorySelected(bool selected, categoryId) {
    if (selected == true) {
      setState(() {
        widget.selectedList.add(categoryId);
      });
    } else {
      setState(() {
        widget.selectedList.remove(categoryId);
      });
    }
    
    // Callback to save the updated selectedList to MyHomeWidget list
    widget.callback(widget.selectedList);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Interests"),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            Text(
              "Select your interests: ",
              style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
            ),
            ListView.builder(
              physics: NeverScrollableScrollPhysics(),
              scrollDirection: Axis.vertical,
              shrinkWrap: true,
              itemCount: _categories['responseTotalResult'],
              itemBuilder: (BuildContext context, int index) {
                return CheckboxListTile(
                  value: widget.selectedList.contains(
                      _categories['responseBody'][index]['category_id']),
                  onChanged: (bool selected) {
                    _onCategorySelected(selected,
                        _categories['responseBody'][index]['category_id']);
                  },
                  title:
                      Text(_categories['responseBody'][index]['category_name']),
                );
              },
            ),
            RaisedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: Text("Go back!!"),
            ),
          ],
        ),
      ),
    );
  }
}
  

Вот метод, из которого вы хотели извлечь Firebase . Я использовал обновленный класс FirebaseFirestore . Если вы используете более старую версию Firebase , просто замените FirebaseFirestore на Firebase .

 Future<void> fetchInterestChanges() async { //function to get the changes in database.
    final DocumentSnapshot doc = await FirebaseFirestore.instance
        .collection('my_users')
        .document(currentUserModel.id)
        .get();
    
    final updatedList = doc.data();
    print(updatedList);
  }
  

Комментарии:

1. Спасибо. Это решило задачу. Хитрость здесь в том, чтобы сохранить список в MyHomeWidget, используя функцию обратного вызова и передавая список из родительского виджета. Использование поставщика также послужило бы цели, верно? и какой из них является лучшим вариантом, учитывая этот сценарий .. setstate или Provider?

2. Да, вы Provider также можете использовать. Но для небольшого количества виджетов, как в вашем случае, я думаю StatefulWidget , будет работать нормально. Provider следует использовать, когда вам нужно получить доступ или передать данные между виджетами глубокой иерархии, где передача аргументов в конструкторе действительно утомительна, повторяющаяся и грязная.

3. Понял! У меня есть другой запрос. Вышеупомянутый код является лишь частью более крупного приложения. Я должен сохранить выбранные интересы в базе данных firestore и использовать firestore для отображения выбранных интересов (что мы в настоящее время делаем, передавая данные через конструктор). Можете ли вы также поделиться тем, как мы можем этого добиться? Я просто обновляю код в вопросе

4. я обновил код и добавил комментарии, в которых я написал функцию. Если бы вы могли мне помочь с этим?

5. Вам просто нужно вызвать get метод вместо update того, чтобы извлекать данные Firebase и анализировать их. Кроме того, я думаю, что вы используете более старую версию Firebase as now, вы должны использовать FirebaseFirestore.instance вместо Firestore.instance .

Ответ №2:

Вы должны смешивать с классом AutomaticKeepAliveClientMixin , чтобы сохранить старые виджеты, например: https://github.com/diegoveloper/flutter-samples/blob/master/lib/persistent_tabbar/page2.dart .

Или когда не работает:

  1. Вы должны сохранить флажок data в кеш, например, в общих присутствиях и т. Д.
  2. При повторном открытии страницы вы вызываете данные из кэша
  3. И когда установлен флажок / не сохранять данные в кеш