Наследование StatefulWidgets вызывает ошибку контекста скаффолда - PullRequest
0 голосов
/ 18 июня 2020

Я пытаюсь создавать виджеты для построения представлений таким образом, чтобы мне не приходилось каждый раз вводить один и тот же код. Вот что я придумал:

/// 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, например:

    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);
  }
}

Все работает нормально, за исключением случаев, когда я пытаюсь показать Snackbar, функция _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): ════════════════════════════════════════════════════════════════════════════════════════════════════

Мне что-то не хватает, чтобы выполнить sh что я пытаюсь сделать? Я начал трепетать всего через несколько дней go, поэтому я не удивлюсь, если я делаю это неправильно.

1 Ответ

0 голосов
/ 18 июня 2020

Проблема в том, что вы создаете CBandeira> GerenciadorTelaLogadaCadastro> GerenciadorTelaLogada, и вместо того, чтобы позволить StatefulWidget выполнять и вызывать метод сборки каждого из них, когда это необходимо, вы вызываете их внутри других методов сборки при использовании .build(context), делая это, вы говорите классу использовать контекст, который вы им даете (вот почему ошибка, он не может найти каркас в дереве, потому что вы используете тот же контекст), вместо того, чтобы позволить StatefulWidget создать свой собственный ниже в дереве. Возьмем, к примеру, GMenu (), это виджет, который вы создаете где-то еще и из-за него можете повторно использовать в любое время. сначала вам нужно построить Scaffold, затем внутри тела, который вы хотите обернуть другим классом, и с помощью этого logi c вы go, пока не дойдете до минимального класса или виджета, который вам нужен в дереве.

//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),
      ),
    );
  }
}

ОБНОВЛЕНИЕ

Я использовал Scaffold перед созданием виджета, поэтому я не видел ошибки, теперь я ее вижу, и что вам нужно сделать в этом случае, это обернуть виджет, который вы хотите использовать Scaffold.of(context), с помощью Builder, он позволяет вам создать новый контекст и после этого он может проверить выше в дереве для помоста. До этого они оба находятся в одном контексте, поэтому он не нашел его

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),
      ),
    );
  }
}

enter image description here

ОБНОВЛЕНИЕ С ГЛОБАЛЬНЫМ КЛЮЧОМ

class GerenciadorTelaLogada extends StatelessWidget {
  final String titulo;
  final Widget child;
  final Widget floatActButton;
  final GlobalKey<ScaffoldState> scaffoldKey;

  const GerenciadorTelaLogada(
      {this.child, @required this.titulo, @required this.scaffoldKey, this.floatActButton});

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

      floatingActionButton: this.floatActButton,

      // Side Menu
      //drawer: GMenu(),

      // Corpo do DashBoard
      body: SafeArea(
          child: GestureDetector(
        onTap: () {
          FocusScope.of(context).unfocus();
        },
        child: Container(
          child: this.child ?? const SizedBox(),
          color: Color(0xff5b71b3),
          padding: EdgeInsets.only(top: 10),
        ),
      )),
    );
  }
}

class GerenciadorTelaLogadaCadastro extends StatelessWidget {
  /// Função executada quando clicar no botão de salvar
  final VoidCallback onSalvar;
  final GlobalKey<ScaffoldState> scaffoldKey;
  final String titulo;
  final Widget child;

  /// Construtor
  const GerenciadorTelaLogadaCadastro(
      {@required this.titulo, @required this.scaffoldKey, this.child, @required this.onSalvar});

// Costroi a parte excluisa das telas de cadastro
  @override
  Widget build(BuildContext context) {
    /// Retorna a tela base construida com as informações possiveis em uma tela.
    return GerenciadorTelaLogada(
      scaffoldKey: scaffoldKey, //pass it to the scaffold
      titulo: this.titulo,
      child: this.child,
      floatActButton: FloatingActionButton(
        child: Icon(
          Icons.save,
          color: Colors.white,
        ),
        backgroundColor: Colors.green,
        onPressed: this.onSalvar,
      ),
    );
  }
}

//Just created this but I don't know how it really looks like
class GTextBox{
  final String label;
  final IconData icon;

  GTextBox({this.label, this.icon});
}

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

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

  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) {
    return GerenciadorTelaLogadaCadastro(
      scaffoldKey: scaffoldKey, //pass the GlobalKey to the Scaffold
      onSalvar: () {
        if (_formKey.currentState.validate()) {
          //use the scaffoldKey instead
          scaffoldKey.currentState.showSnackBar(SnackBar(
            content: Text("Form is valid"),
          ));
        }
      },
      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),
      ),
    );
  }
}
...