设置GetxController内的RxBool的新值时出现问题

huangapple go评论55阅读模式
英文:

GETX Problem setting new value of an RxBool inside GetxController

问题

以下是代码的翻译部分:

我刚开始学习Flutter中的GetX,因为我试图改善我的应用程序状态。用户可以选择“Instant”或“Appointments”,如果选择“Instant”,则会创建一个Firebase文档,并将用户置于搜索状态。主要目标是在用户返回主页时在底部显示一个小部件,例如“正在搜索用户...”。因此,这是我对状态管理的简介。

**错误**
```dart
发生异常。
FlutterError(在构建期间调用了setState()markNeedsBuild()
无法将此Obx小部件标记为需要构建,因为框架已经在构建小部件。只有在其祖先中的一个当前正在构建时,才能在构建阶段标记小部件需要构建。允许发生此异常,因为框架在构建父小部件之前构建子小部件,这意味着脏后代将始终被构建。否则,在此构建阶段,框架可能不会访问此小部件。
调用setState()markNeedsBuild()的小部件是:
  Obx
在进行有害调用时当前正在构建的小部件是:
  FindInstantWidget

order_screen.dart

class FindInstantWidget extends GetWidget {
  const FindInstantWidget({Key? key, required this.text, required this.docId})
      : super(key: key);

  final String? text;
  final String? docId;

  @override
  Widget build(BuildContext context) {

    final controller = Get.put(SearchingController());

    if (controller.isSearching.isFalse) {
      controller.startSearch(docId);
    }

    return Obx(() => controller.isAccepted.isFalse
        ? Scaffold(
            body: Container(
              margin: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    margin: const EdgeInsets.all(20),
                    child: const CircularProgressIndicator(
                      backgroundColor: Colors.grey,
                      color: Colors.purple,
                      strokeWidth: 5,
                    ),
                  ),
                  Container(
                    margin: const EdgeInsets.all(20),
                    child: Text("Searching for $text"),
                  ),
                  const SizedBox(height: 16),
                  ButtonWidget(
                      text: 'Cancel',
                      onClicked: () async {
                        //Database().deleteInstant(docId);
                        //Navigator.pop(context);
                        controller.stopSearch();
                        DocumentReference docRef =
                            _firestore.collection('jobs').doc(docId);
                        Database().deleteInstant(docRef);
                        Get.offAll(() => LandingPage(title: "Landing Page"));
                      })
                ],
              ),
            ),
          )
        : const Scaffold(
            body: Text("Found someone for you!"),
          ));
  }
}

searchingController.dart

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:get/get.dart';

class SearchingController extends GetxController {
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;
  RxBool isSearching = false.obs;
  RxBool isAccepted = false.obs;
  RxString? uidAcceptor;
  final jobType = Rxn<String>();
  final docId = Rxn<String>();
  StreamSubscription? _streamSubscription;

  void startSearch(String? id) {
    DocumentReference docRef = _firestore.collection('jobs').doc(id);

    docId.value = docRef.id;
    isSearching.value = true;

    try {
      _streamSubscription = docRef.snapshots().listen((e) async {
        Map a = e.data() as Map<String, dynamic>;
        uidAcceptor?.value = a['uidAccepted'];
        isAccepted.value = a['isAccepted'];
        jobType.value = a['jobType'];
      });
      update();
    } catch (e) {
      print(e);
    }
  }

  void stopSearch() {
    isSearching.value = false;
    _streamSubscription?.cancel();
  }
}

landing_screen.dart

class LandingPage extends GetWidget {
  LandingPage({Key? key, required this title}) : super(key: key);
  final String title;

  BottomNavigationController bottomNavigationController =
      Get.put(BottomNavigationController());
  SearchingController searchingController = Get.put(SearchingController());

  final screens = [
    const HomePage(),
    const OrderPage(),
    const AccountPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Obx(() => Stack(alignment: Alignment.center, children: [
              IndexedStack(
                index: bottomNavigationController.selectedIndex.value,
                children: screens,
              ),
              searchingController.isSearching.isTrue
                  ? Positioned(
                      bottom: 10,
                      child: GestureDetector(
                          onTap: () {
                            Get.to(FindInstantWidget(
                                text: searchingController.jobType.value,
                                docId: searchingController.docId.value));
                          },
                          child: Container(
                            decoration: const BoxDecoration(color: Colors.grey),
                            width: 250,
                            child: Padding(
                              padding: const EdgeInsets.all(10),
                              child: Text(
                                "Searching for ${searchingController.jobType.value}...",
                                style: const TextStyle(
                                    fontFamily: 'Roboto',
                                    fontSize: 16,
                                    color: Colors.black,
                                    decoration: TextDecoration.none,
                                    fontWeight: FontWeight.normal),
                                textAlign: TextAlign.center,
                              ),
                            ),
                          )))
                  : const SizedBox(width: 0, height: 0)
            ])),
        bottomNavigationBar: Obx(
          () => BottomNavigationBar(
              currentIndex: bottomNavigationController.selectedIndex.value,
              fixedColor: Colors.green,
              items: const [
                BottomNavigationBarItem(
                  label: "Home",
                  icon: Icon(Icons.home),
                ),
                BottomNavigationBarItem(
                  label: "Orders",
                  icon: Icon(Icons.article_outlined),
                ),
                BottomNavigationBarItem(
                  label: "Account",
                  icon: Icon(Icons.account_circle_outlined),
                ),
              ],
              onTap: (index) => bottomNavigationController.changeIndex(index)),
        ));
  }
}

如果您需要更多帮助或有其他问题,请随时提出。

英文:

I just started learning about GetX in Flutter as i'm trying to improve my application states. The user has the option to select "Instant" or "Appointments" so if "Instant" is clicked a firebase document is created and the user is now in searching state. The main goal was to display a small widget at the bottom when the user returns to the main page, such as "Searching for user...".Thus, my introduction to state management.

Error

Exception has occurred.
FlutterError (setState() or markNeedsBuild() called during build.
This Obx widget cannot be marked as needing to build because the framework is already 
in the process of building widgets. A widget can be marked as needing to be built 
during the build phase only if one of its ancestors is currently building. This 
exception is allowed because the framework builds parent widgets before children, 
which means a dirty descendant will always be built. Otherwise, the framework might 
not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
  Obx
The widget which was currently being built when the offending call was made was:
  FindInstantWidget)

order_screen.dart

class FindInstantWidget extends GetWidget {
  const FindInstantWidget({Key? key, required this.text, required this.docId})
      : super(key: key);

  final String? text;
  final String? docId;

  @override
  Widget build(BuildContext context) {

    final controller = Get.put(SearchingController());

    if (controller.isSearching.isFalse) {
      controller.startSearch(docId);
    }

    return Obx(() =&gt; controller.isAccepted.isFalse
        ? Scaffold(
            body: Container(
              margin: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    margin: const EdgeInsets.all(20),
                    child: const CircularProgressIndicator(
                      backgroundColor: Colors.grey,
                      color: Colors.purple,
                      strokeWidth: 5,
                    ),
                  ),
                  Container(
                    margin: const EdgeInsets.all(20),
                    child: Text(&quot;Searching for $text&quot;),
                  ),
                  const SizedBox(height: 16),
                  ButtonWidget(
                      text: &#39;Cancel&#39;,
                      onClicked: () async {
                        //Database().deleteInstant(docId);
                        //Navigator.pop(context);
                        controller.stopSearch();
                        DocumentReference docRef =
                            _firestore.collection(&#39;jobs&#39;).doc(docId);
                        Database().deleteInstant(docRef);
                        Get.offAll(() =&gt; LandingPage(title: &quot;Landing Page&quot;));
                      })
                ],
              ),
            ),
          )
        : const Scaffold(
            body: Text(&quot;Found someone for you!&quot;),
          ));
  }
}

searchingController.dart

import &#39;dart:async&#39;;

import &#39;package:cloud_firestore/cloud_firestore.dart&#39;;
import &#39;package:get/get.dart&#39;;

class SearchingController extends GetxController {
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;
  RxBool isSearching = false.obs;
  RxBool isAccepted = false.obs;
  RxString? uidAcceptor;
  final jobType = Rxn&lt;String&gt;();
  final docId = Rxn&lt;String&gt;();
  StreamSubscription? _streamSubscription;

  void startSearch(String? id) {
    DocumentReference docRef = _firestore.collection(&#39;jobs&#39;).doc(id);

    docId.value = docRef.id;
    isSearching.value = true;

    try {
      _streamSubscription = docRef.snapshots().listen((e) async {
        Map a = e.data() as Map&lt;String, dynamic&gt;;
        uidAcceptor?.value = a[&#39;uidAccepted&#39;];
        isAccepted.value = a[&#39;isAccepted&#39;];
        jobType.value = a[&#39;jobType&#39;];
      });
      update();
    } catch (e) {
      print(e);
    }
  }

  void stopSearch() {
    isSearching.value = false;
    _streamSubscription?.cancel();
  }
}

landing_screen.dart

class LandingPage extends GetWidget {
  LandingPage({Key? key, required this.title}) : super(key: key);
  final String title;

  BottomNavigationController bottomNavigationController =
      Get.put(BottomNavigationController());
  SearchingController searchingController = Get.put(SearchingController());

  final screens = [
    const HomePage(),
    const OrderPage(),
    const AccountPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Obx(() =&gt; Stack(alignment: Alignment.center, children: [
              IndexedStack(
                index: bottomNavigationController.selectedIndex.value,
                children: screens,
              ),
              searchingController.isSearching.isTrue
                  ? Positioned(
                      bottom: 10,
                      child: GestureDetector(
                          onTap: () {
                            Get.to(FindInstantWidget(
                                text: searchingController.jobType.value,
                                docId: searchingController.docId.value));
                          },
                          child: Container(
                            decoration: const BoxDecoration(color: Colors.grey),
                            width: 250,
                            child: Padding(
                              padding: const EdgeInsets.all(10),
                              child: Text(
                                &quot;Searching for ${searchingController.jobType.value}...&quot;,
                                style: const TextStyle(
                                    fontFamily: &#39;Roboto&#39;,
                                    fontSize: 16,
                                    color: Colors.black,
                                    decoration: TextDecoration.none,
                                    fontWeight: FontWeight.normal),
                                textAlign: TextAlign.center,
                              ),
                            ),
                          )))
                  : const SizedBox(width: 0, height: 0)
            ])),
        bottomNavigationBar: Obx(
          () =&gt; BottomNavigationBar(
              currentIndex: bottomNavigationController.selectedIndex.value,
              fixedColor: Colors.green,
              items: const [
                BottomNavigationBarItem(
                  label: &quot;Home&quot;,
                  icon: Icon(Icons.home),
                ),
                BottomNavigationBarItem(
                  label: &quot;Orders&quot;,
                  icon: Icon(Icons.article_outlined),
                ),
                BottomNavigationBarItem(
                  label: &quot;Account&quot;,
                  icon: Icon(Icons.account_circle_outlined),
                ),
              ],
              onTap: (index) =&gt; bottomNavigationController.changeIndex(index)),
        ));
  }
}

It was working before, when I returned to the landing screen the widget was displayed as it should, but as I was learning to removing it and cancelling the firebase listen subscription, it doesn't seem to work properly anymore. The widget would appear if I reload the landing widget by clicking on other icons on the nav bar. Seems GetX is not properly updating the views?

The reason that my head is spinning is probably this line (also highlighted)
isSearching.value = true; inside searchingController.dart

SS of Exception(https://i.stack.imgur.com/AOnOV.png)

It barely works if I moved it into the try-catch block but it seems like prolonging my descend into madness from all the other issues that arises.

When initialising my controllers, this does appear on my debug console, it's been bothering me but I have little understand of the "GetLifeCycleBase?"

[GETX] Instance &quot;GetMaterialController&quot; has been created
[GETX] Instance &quot;GetMaterialController&quot; has been initialized
[GETX] Instance &quot;GetLifeCycleBase?&quot; is not registered.
[GETX] Instance &quot;AuthController&quot; has been created
[GETX] Instance &quot;AuthController&quot; has been initialized
[GETX] Instance &quot;BottomNavigationController&quot; has been created
[GETX] Instance &quot;BottomNavigationController&quot; has been initialized

答案1

得分: 0

几乎可以确定是因为 controller.startSearch(docId); 在您的 FindInstantWidget 的构建函数内部。

请尝试这样修改:

if (controller.isSearching.isFalse) {
      controller.startSearch(docId);
    }

改为:

WidgetsBinding.instance.addPostFrameCallback((_) {
      if (controller.isSearching.isFalse) {
        controller.startSearch(docId);
      }
    });
英文:

Almost sure it's because that controller.startSearch(docId); inside the build function on your FindInstantWidget.

Instead of this:

if (controller.isSearching.isFalse) {
      controller.startSearch(docId);
    }

Try this:

WidgetsBinding.instance.addPostFrameCallback((_) {
      if (controller.isSearching.isFalse) {
        controller.startSearch(docId);
      }
    });

huangapple
  • 本文由 发表于 2023年3月9日 20:22:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/75684565.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定