继承StatefulWidgets导致支架上下文错误

时间:2020-06-18 05:59:03

标签: flutter dart

我试图创建小部件来构建视图,而不必每次都键入相同的代码。这是我想出的:

/// Common class for all views
class GerenciadorTelaLogada<C extends StatefulWidget> extends State<C> {
  /// View title
  final String titulo;

  // Child widgets
  final Widget child;

  GerenciadorTelaLogada(
      {@required this.titulo, this.child, this.floatingActButton});

  final Widget floatingActButton;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: Text(this.titulo),
        centerTitle: true,
      ),

      // Side Menu
      drawer: GMenu(),

      floatingActionButton: this.floatingActButton,

      // View body
      body: SafeArea(
        child: GestureDetector(
          onTap: () {
            FocusScope.of(context).requestFocus(new FocusNode());
          },
          child: Container(
            color: Color(0xff5b71b3),
            padding: EdgeInsets.only(top: 10),
            child: this.child,
          ),
        ),
      ),
    );
  }
}

然后,我创建了另一个类,为GerenciadorTelaLogada类添加了一些功能:

// Standard class to all views that as a Form
class GerenciadorTelaLogadaCadastro<C extends StatefulWidget>
    extends GerenciadorTelaLogada<C> {

  /// Executed when clicked in save button
  final VoidCallback onSalvar;

  /// Construtor
  GerenciadorTelaLogadaCadastro(
      {@required String titulo, Widget child, @required this.onSalvar})
      : super(titulo: titulo, child: child);

// Adds new widgets
  @override
  Widget build(BuildContext context) {
    FloatingActionButton salvarFloatButton = FloatingActionButton(
      child: Icon(
        Icons.save,
        color: Colors.white,
      ),
      backgroundColor: Colors.green,
      onPressed: this.onSalvar,
    );

/// Return the base view with additional widgets
    return GerenciadorTelaLogada(
      titulo: this.titulo,
      child: this.child,
      floatingActButton: salvarFloatButton,
    ).build(context);
  }
}

现在,我可以使用该类创建多个视图,如下所示:

    class CBandeira extends StatefulWidget {
      @override
      _CBandeiraState createState() => _CBandeiraState();
    }



class _CBandeiraState extends GerenciadorTelaLogadaCadastro<CBandeira>
    with SingleTickerProviderStateMixin {
  final _formKey = GlobalKey<FormState>();

var formInputs = <GTextBox>[];

@override
  Widget build(BuildContext context) {
    return GerenciadorTelaLogadaCadastro(
      onSalvar: () {
        if (_formKey.currentState.validate()) {
          Scaffold.of(context).showSnackBar(new SnackBar(
            content: Text("Test"),
          ));
        }
      },
      titulo: "Cadastro de Bandeira",
      child: Form(
        key: _formKey,
        child: ListView.separated(
            itemBuilder: (context, index) {
              return ListTile(
                leading: Icon(
                  formInputs[index].icon,
                  color: Colors.white,
                ),
                title: formInputs[index],
              );
            },
            separatorBuilder: (context, index) {
              return SizedBox(height: 10);
            },
            itemCount: formInputs.length),
      ),
    ).build(context);
  }
}

一切正常,除了当我尝试显示快餐栏时,_CBandeiraState, onSalvar函数返回此错误:

I/flutter (20636): ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
I/flutter (20636): The following assertion was thrown while handling a gesture:
I/flutter (20636): Scaffold.of() called with a context that does not contain a Scaffold.
I/flutter (20636): No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This
I/flutter (20636): usually happens when the context provided is from the same StatefulWidget as that whose build
I/flutter (20636): function actually creates the Scaffold widget being sought.
I/flutter (20636): There are several ways to avoid this problem. The simplest is to use a Builder to get a context that
I/flutter (20636): is "under" the Scaffold. For an example of this, please see the documentation for Scaffold.of():
I/flutter (20636):   https://api.flutter.dev/flutter/material/Scaffold/of.html
I/flutter (20636): A more efficient solution is to split your build function into several widgets. This introduces a
I/flutter (20636): new context from which you can obtain the Scaffold. In this solution, you would have an outer widget
I/flutter (20636): that creates the Scaffold populated by instances of your new inner widgets, and then in these inner
I/flutter (20636): widgets you would use Scaffold.of().
I/flutter (20636): A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, then use the
I/flutter (20636): key.currentState property to obtain the ScaffoldState rather than using the Scaffold.of() function.
I/flutter (20636): The context used was:
I/flutter (20636):   CBandeira
I/flutter (20636):
I/flutter (20636): When the exception was thrown, this was the stack:
I/flutter (20636): #0      Scaffold.of (package:flutter/src/material/scaffold.dart:1456:5)
I/flutter (20636): #1      _CBandeiraState.build.<anonymous closure> (package:gerenciador/views/core/cartao/c_bandeira.dart:43:20)
I/flutter (20636): #2      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:779:19)
I/flutter (20636): #3      _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:862:36)
I/flutter (20636): #4      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
I/flutter (20636): #5      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:504:11)
I/flutter (20636): #6      BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:282:5)
I/flutter (20636): #7      BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:217:7)
I/flutter (20636): #8      PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:475:9)
I/flutter (20636): #9      PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:76:12)
I/flutter (20636): #10     PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:122:9)
I/flutter (20636): #11     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:379:8)
I/flutter (20636): #12     PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:120:18)
I/flutter (20636): #13     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:106:7)
I/flutter (20636): #14     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:218:19)
I/flutter (20636): #15     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:198:22)
I/flutter (20636): #16     GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:156:7)
I/flutter (20636): #17     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:102:7)
I/flutter (20636): #18     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:86:7)
I/flutter (20636): #22     _invoke1 (dart:ui/hooks.dart:275:10)
I/flutter (20636): #23     _dispatchPointerDataPacket (dart:ui/hooks.dart:184:5)
I/flutter (20636): (elided 3 frames from dart:async)
I/flutter (20636):
I/flutter (20636): Handler: "onTap"
I/flutter (20636): Recognizer:
I/flutter (20636):   TapGestureRecognizer#033d5
I/flutter (20636): ════════════════════════════════════════════════════════════════════════════════════════════════════

我是否缺少某些东西来完成我想做的事情?我几天前才开始发抖,所以如果我做错了方向,我也不会感到惊讶。

1 个答案:

答案 0 :(得分:0)

问题是您正在创建CBandeira> GerenciadorTelaLogadaCadastro> GerenciadorTelaLogada,而不是让StatefulWidget做并在需要时调用每个构建方法,而是在使用{{1}时在其他构建方法中调用它们},这样做是要告诉该类使用为其提供的上下文(这就是错误的原因,因为您使用的是相同的上下文,所以它无法在树中找到支架),而不是让StatefulWidget创建其在树下拥有。以GMenu()为例,它是您在其他地方创建的小部件,因此可以随时重用。

您需要从顶部小部件(包裹其他类的父类)到底部进行构建,因此首先需要构建Scaffold,然后在内部要包装另一个类的主体,并按照这种逻辑直到找到树中所需的最小的类或小部件为止。

.build(context)

然后,您可以将其与其他任何类一起使用

//If you're not going to use setState or doens't need to update the widget then just use a StatelessWidget
class GerenciadorTelaLogada extends StatelessWidget {
  final String titulo;
  final Widget child;
  final Widget floatingActButton;

  GerenciadorTelaLogada(
      {@required this.titulo, this.child, this.floatingActButton});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: Text(this.titulo),
        centerTitle: true,
      ),
      drawer: GMenu(),
      floatingActionButton: this.floatingActButton,
      body: SafeArea(
        child: GestureDetector(
          onTap: () {
            FocusScope.of(context).requestFocus(new FocusNode());
          },
          child: Container(
            color: Color(0xff5b71b3),
            padding: EdgeInsets.only(top: 10),
            child: this.child ?? const SizedBox(),
          ),
        ),
      ),
    );
  }
}

更新

我在创建窗口小部件之前使用了脚手架,这就是为什么我没有看到错误的原因,现在我看到了它,在这种情况下,您要做的是将要使用class CBandeira extends StatefulWidget { @override _CBandeiraState createState() => _CBandeiraState(); } class _CBandeiraState extends State<CBandeira> { final _formKey = GlobalKey<FormState>(); var formInputs = <GTextBox>[]; @override Widget build(BuildContext context) { // Now you can use it in other's widget build method and create your own child, title and fab return GerenciadorTelaLogada( titulo: "Cadastro de Bandeira", floatingActButton: FloatingActionButton( child: Icon( Icons.save, color: Colors.white, ), backgroundColor: Colors.green, onPressed: () { if (_formKey.currentState.validate()) { Scaffold.of(context).showSnackBar(new SnackBar( content: Text("Test"), )); } }, ), child: Form( key: _formKey, child: ListView.separated( itemBuilder: (context, index) { return ListTile( leading: Icon( formInputs[index].icon, color: Colors.white, ), title: formInputs[index], ); }, separatorBuilder: (context, index) { return SizedBox(height: 10); }, itemCount: formInputs.length), ), ); } } 的窗口小部件包装起来一个Builder,它允许您创建一个新的上下文,然后它可以在树中的上方检查支架。在此之前,两者都处于同一环境中,这就是为什么它找不到它的原因

Scaffold.of(context)

enter image description here

使用全局密钥更新

class CBandeira extends StatefulWidget {
      @override
      _CBandeiraState createState() => _CBandeiraState();
    }


class _CBandeiraState extends State<CBandeira> {
  final _formKey = GlobalKey<FormState>();

var formInputs = <GTextBox>[
GTextBox(
  label: "Código",
  tipo: TextBoxTipo.numerico,
  icon: Icons.person,
),
GTextBox(
  label: "Nome",
  tipo: TextBoxTipo.tudo,
  icon: Icons.person,
  validator: (valor) {
    if (valor.length < 3) {
      return 'Nome muito curto';
    }
    return null;
  },
),
GTextBox(
  label: "Descrição",
  tipo: TextBoxTipo.tudo,
  icon: Icons.details,
),
];

@override
  Widget build(BuildContext context) {
    // Now you can use it in other's widget build method and create your own child, title and fab
    return GerenciadorTelaLogada(
       titulo: "Cadastro de Bandeira",
       floatingActButton: Builder( //with this It can now check for a scaffold above the tree
         builder: (context){
           return  FloatingActionButton(
         child: Icon(
           Icons.save,
           color: Colors.white,
         ),
         backgroundColor: Colors.green,
         onPressed: () {
           if (_formKey.currentState.validate()) {
             Scaffold.of(context).showSnackBar(new SnackBar(
               content: Text("Test"),
             ));
           }
         },
       );
         },
       ),
      child: Form(
        key: _formKey,
        child: ListView.separated(
            itemBuilder: (context, index) {
              return ListTile(
                leading: Icon(
                  formInputs[index].icon,
                  color: Colors.white,
                ),
                title: formInputs[index],
              );
            },
            separatorBuilder: (context, index) {
              return SizedBox(height: 10);
            },
            itemCount: formInputs.length),
      ),
    );
  }
}