
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
MyWidget
exemplo 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
Style
ou 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
const
que 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 issoInheritedWidget
e 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çãoupdateShouldNotify
implementada 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
MyInheritedWidget
o 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.push
essas 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 Student
da 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!