InheritedWidget in Flutter

A tradução do artigo foi preparada para alunos do curso Flutter Mobile Developer .










As raízes das árvores de widget do Flutter podem ser muito profundas ...







Muito profundas.



A natureza dos componentes dos widgets Flutter permite designs de aplicativos muito elegantes, modulares e flexíveis. No entanto, isso também pode resultar em muito código clichê para a passagem de contexto. Veja o que acontece quando queremos passar o accountId e o scopeId da página para o widget dois níveis abaixo:



class MyPage extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyPage(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    return new MyWidget(accountId, scopeId);
  }
}

class MyWidget extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyWidget(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    // -   
    new MyOtherWidget(accountId, scopeId);
    ...
  }
}

class MyOtherWidget extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyOtherWidget(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    //  
    ...


Se não for verificado, esse padrão pode facilmente se espalhar por toda a base de código. Parametrizamos pessoalmente mais de 30 widgets dessa forma. Quase metade do tempo de trabalho, o widget recebia parâmetros apenas para repassá-los adiante, como no MyWidgetexemplo acima.



O estado de MyWidget é independente de parâmetros e, ainda assim, ele reconstrói toda vez que os parâmetros mudam!


Claro, deve haver uma maneira melhor ...



Apresentando InheritedWidget .



Resumindo, é um tipo especial de widget que define o contexto na raiz de uma subárvore. Ele pode fornecer esse contexto de maneira eficiente para cada widget nessa subárvore. O padrão de acesso deve ser familiar para um desenvolvedor Flutter:



final myInheritedWidget = MyInheritedWidget.of(context);


Este contexto é apenas uma classe Dart. Portanto, ele pode conter tudo o que você deseja inserir. Muitos dos contextos de Flutter comumente usados, como Styleou MediaQuery, nada mais são do que Widgets herdados que vivem no mesmo nível MaterialApp.



Se complementarmos o exemplo acima usando InheritedWidget, isso é o que obtemos:



class MyInheritedWidget extends InheritedWidget {
  final int accountId;
  final int scopeId;

  MyInheritedWidget(accountId, scopeId, child): super(child);
  
  @override
  bool updateShouldNotify(MyInheritedWidget old) =>
    accountId != old.accountId || scopeId != old.scopeId;
}

class MyPage extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyPage(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      accountId,
      scopeId,
      const MyWidget(),
     );
  }
}

class MyWidget extends StatelessWidget {

  const MyWidget();
  
  Widget build(BuildContext context) {
    // -   
    const MyOtherWidget();
    ...
  }
}

class MyOtherWidget extends StatelessWidget {

  const MyOtherWidget();
  
  Widget build(BuildContext context) {
    final myInheritedWidget = MyInheritedWidget.of(context);
    print(myInheritedWidget.scopeId);
    print(myInheritedWidget.accountId);
    ...


É importante observar:



  • Os construtores são agora o constque torna esses widgets armazenáveis ​​em cache; o que aumenta a produtividade.
  • Quando os parâmetros são atualizados, um novo é criado MyInheritedWidget. No entanto, ao contrário do primeiro exemplo, a subárvore não é reconstruída. Em vez disso, o Flutter mantém um registro interno que rastreia os widgets que acessaram isso InheritedWidgete apenas reconstrói aqueles que usam esse contexto. Neste exemplo, é MyOtherWidget.
  • Se a árvore estiver sendo reconstruída por motivos diferentes de alterações de parâmetro, como alterações de orientação, seu código ainda pode construir um novo InheritedWidget. No entanto, como os parâmetros permanecem os mesmos, os widgets na subárvore não serão notificados. Este é o propósito da função updateShouldNotifyimplementada por você InheritedWidget.


Finalmente, vamos falar sobre boas práticas.



InheritedWidget deve ser pequeno



Sobrecarregá-los com muito contexto leva à perda da segunda e terceira vantagens mencionadas acima, uma vez que o Flutter não pode determinar qual parte do contexto está sendo atualizada e qual parte está sendo usada por widgets. Em vez de:



class MyAppContext {
  int teamId;
  String teamName;
  
  int studentId;
  String studentName;
  
  int classId;
  ...
}


Prefiro fazer:



class TeamContext {
  int teamId;
  String teamName;
}

class StudentContext {
  int studentId;
  String studentName;
}
 
class ClassContext {
  int classId;
  ...
}


Use const para criar seus widgets



Sem const, não há reconstrução seletiva da subárvore. O Flutter cria uma nova instância de cada widget na subárvore e a invoca build(), desperdiçando ciclos preciosos, especialmente se seus métodos de construção forem pesados ​​o suficiente.



Fique de olho no escopo do seu InheritedWidget-s



InheritedWidget-y são colocados na raiz da árvore de widgets. Isso, de fato, determina seu escopo. Em nossa equipe, descobrimos que ser capaz de declarar contexto em qualquer lugar na árvore de widgets era um exagero. Decidimos restringir nossos widgets contextuais para aceitar apenas Scaffold(ou seus derivados) como filhos. Dessa forma, garantimos que o contexto mais granular pode estar no nível da página e obtemos dois escopos:



  • Widgets no nível do aplicativo, como MediaQuery. Eles estão disponíveis para qualquer widget em qualquer página de seu aplicativo, pois estão localizados na raiz da árvore de widgets de seu aplicativo.
  • Widgets de nível de página como MyInheritedWidgeto exemplo acima.


Você deve escolher um ou outro dependendo de onde o contexto se aplica.



Widgets de nível de página não podem atravessar uma fronteira de rota



Parece óbvio. No entanto, isso tem implicações sérias porque a maioria dos aplicativos tem mais de um nível de navegação. Esta é a aparência de seu aplicativo:



> School App [App Context]
  > Student [Student Context]
    > Grades
    > Bio
  > Teacher [Teacher Context]
    > Courses
    > Bio


Isso é o que Flutter vê:



> School App [App Context]
  > Student [Student Context]
  > Student Grades
  > Student Bio
  > Teacher [Teacher Context]
  > Teacher Courses
  > Teacher Bio


Da perspectiva do Flutter, não há hierarquia de navegação. Cada página (ou cadafalso) é uma árvore de widgets associados a um widget de aplicativo. Portanto, quando você usa Navigator.pushessas páginas para exibição, elas não herdam o widget que carrega o contexto pai. No exemplo acima, você precisará passar explicitamente o contexto Studentda página do Aluno para a página da Biografia do Aluno.



Embora existam diferentes maneiras de passar contexto, sugiro a parametrização de rotas da maneira antiga (por exemplo, codificação de URL se você estiver usando rotas nomeadas). Ele também garante que as páginas possam ser criadas puramente com base na rota, sem a necessidade de usar o contexto da página principal.



Boa codificação!



Chegue a tempo para o curso!



All Articles