5
respostas

[Dúvida] TaskInherit não atualiza em tela

Boa tarde. Estou fazendo o curso de flutter (controller, navegação e estados - 04 Esplorando novos conceitos em widgets). Uma coisa que estou tendo problemas é que mesmo a List de tasks sendo atualizada, isso não é refletido na tela inicial. O que notei, é que no vídeo o instrutor começa já com uma lista pré-carregada, então quando ele coloca mais um item (ele ainda não aparece em tela), mas assim que ele rola a tela o item é carregado. Mas agora vamos pro seguinte cenário.... usando o mesmo fonte do treinamento com a unica differença de começando com uma lista vazia. Após adicionar um item na lista, ele não aparece na tela inicial. Obs: uma forma de fazer aparecer é virando o celular.

A ajuda que preciso é:

  • um código detalhado de como fazer com que a nova task apareça de forma automática, por favor.
  • O melhor caminha seria utilizar key? Se sim... pode me dar um exemplo?

Vou colocar abaixo o código das minhas 2 principais classes:

TaskInherit

import 'package:flutter/material.dart';
import 'package:primeiro_projeto/components/task.dart';

class TaskInherited extends InheritedWidget {
  TaskInherited({
    Key? key,
    required Widget child,
  }) : super(key: key, child: child);

  List<Task> taskList = [];

  void newTask(String name, String photo,int difficulty){
    taskList.add(Task(name, photo, difficulty));
    //  taskList = List.from(taskList)
    //   ..add(Task(name, photo, difficulty));
  }

 @override
  bool updateShouldNotify(TaskInherited oldWidget) {
    return oldWidget.taskList.length != taskList.length;
  }

  static TaskInherited of(BuildContext context) {
    final TaskInherited? result =
        context.dependOnInheritedWidgetOfExactType<TaskInherited>();
    assert(result != null, 'No TaskInherited found in context');
    return result!;
  }
}

Initial_sreen

import 'package:flutter/material.dart';
// import 'package:primeiro_projeto/components/task.dart';
import 'package:primeiro_projeto/data/task_inherited.dart';
import 'package:primeiro_projeto/screens/form_screen.dart';

class InitialScreen extends StatefulWidget {
  const InitialScreen({Key? key}) : super(key: key);

  @override
  State<InitialScreen> createState() => _InitialScreenState();
}

class _InitialScreenState extends State<InitialScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: Container(),
        title: const Text('Tarefas'),
      ),
      body: ListView(
        children: TaskInherited.of(context).taskList,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (contextNew) => FormScreen(taskContext: context,),
            ),
          );
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}
5 respostas

Olá, Willian! Boa noite!

O problema que você está enfrentando parece estar relacionado à atualização da interface do usuário quando a lista de tarefas é modificada. Isso é um comportamento comum quando se trabalha com InheritedWidget, pois ele não notifica automaticamente as mudanças na lista. Vamos tentar resolver isso!

Uma solução prática é usar um StatefulWidget para gerenciar o estado da lista de tarefas. Isso garantirá que a interface do usuário seja atualizada sempre que a lista for modificada. Vou te mostrar como você pode fazer isso:

  1. Modifique o TaskInherited para notificar as mudanças:

    No seu método newTask, adicione um setState para garantir que a interface seja atualizada:

    void newTask(String name, String photo, int difficulty) {
      taskList.add(Task(name, photo, difficulty));
      notifyListeners(); // Notifica que houve uma mudança
    }
    

    No entanto, como InheritedWidget não possui setState, você pode considerar usar um ChangeNotifier ou ValueNotifier para gerenciar o estado.

  2. Use um ChangeNotifier:

    Primeiro, altere a classe TaskInherited para usar ChangeNotifier:

    class TaskInherited extends ChangeNotifier {
      List<Task> taskList = [];
    
      void newTask(String name, String photo, int difficulty) {
        taskList.add(Task(name, photo, difficulty));
        notifyListeners(); // Notifica que houve uma mudança
      }
    }
    
  3. Atualize a InitialScreen para escutar mudanças:

    Em vez de usar InheritedWidget, você pode usar o ChangeNotifierProvider do pacote provider para escutar as mudanças:

    import 'package:flutter/material.dart';
    import 'package:primeiro_projeto/data/task_inherited.dart';
    import 'package:primeiro_projeto/screens/form_screen.dart';
    import 'package:provider/provider.dart';
    
    class InitialScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            leading: Container(),
            title: const Text('Tarefas'),
          ),
          body: Consumer<TaskInherited>(
            builder: (context, taskInherited, child) {
              return ListView(
                children: taskInherited.taskList,
              );
            },
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (contextNew) => FormScreen(taskContext: context),
                ),
              );
            },
            child: const Icon(Icons.add),
          ),
        );
      }
    }
    

Com essas mudanças, sua lista de tarefas deve ser atualizada automaticamente na interface do usuário sempre que você adicionar uma nova tarefa.

Espero ter ajudado e bons estudos!

Com as alterações sugeridas acima, precisei fazer mais algumas alterações em meu fonte, sendo elas:

  • Arquivo main, no home do componente MaterialApp, passei a chamar diretamente o componente InitialScreen())
  • Dentro do form_screen tive que alterar a forma que adiciono uma tarefa a lista, agora preciso instanciar o TaskInherited() e então utlizar o método de newTask dentro dela.

Com as aplicadas e as alterações acima, agora tenho o sseguinte comportamento: 1 - se eu executar o app diretamente rodando pela IDE ela vai até o ponto de instalar no device, mas fica parado nessa tela do icone do flutter no celular e no prompt fica a mensagem de: D/ProfileInstaller( 943): Installing profile for com.example.primeiro_projeto

2 - se eu fechar o app e rodar diretamente pelo celular, aparece a seguinte tela: Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Para facilitar a analise coloquei o fonte neste repositório: https://github.com/Martoszat/project_flutter/tree/main/lib

obrigado desde já!

Olá, Willian!

O erro Could not find the correct provider<TaskInherited> acontece porque o ChangeNotifierProvider não está configurado corretamente na árvore de widgets. Para tentar resolver isso:

  1. Configure o Provider no main.dart: Certifique-se de que o TaskInherited está sendo fornecido no topo da árvore:

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    import 'package:primeiro_projeto/data/task_inherited.dart';
    import 'package:primeiro_projeto/screens/initial_screen.dart';
    
    void main() {
      runApp(
        ChangeNotifierProvider(
          create: (context) => TaskInherited(),
          child: MyApp(),
        ),
      );
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: InitialScreen(),
        );
      }
    }
    
  2. Acesse o Provider no FormScreen: No formulário, use o Provider para acessar o estado, ao invés de instanciar diretamente:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:primeiro_projeto/data/task_inherited.dart';

class FormScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Adicionar Tarefa'),
      ),
      body: ElevatedButton(
        onPressed: () {
          Provider.of<TaskInherited>(context, listen: false).newTask(
            'Nova Tarefa',
            'url_imagem',
            1,
          );
          Navigator.pop(context);
        },
        child: const Text('Adicionar Tarefa'),
      ),
    );
  }
}

Com isso, o Provider será encontrado corretamente, e as atualizações funcionarão sem problemas. Caso o erro persista, verifique se o flutter pub get foi executado e se todas as importações estão corretas.

Espero ter ajudado e bons estudos!

Boa tarde pessoal. Pois o problema persiste. Fiz as ultimas alterações solicitadas, inclusive alterei o método dentro do form para no onPress utilizar o provider e não instanciar como estava fazendo anteriormente. Porem voltamos a estapa inicial, onde após incluir um item na lista e voltar para a página principal ele só aparece se eu virar a tela do celular.

Dentro do form, no onPress passei tanto o context quando o context que recebo via parametros, e ambos tem o mesmo comportamento.

Executei um flutter clean e um flutter pub get e o problema ainda persiste.

Atualizei o meu fonte no git para análise: https://github.com/Martoszat/project_flutter/tree/main/lib

Ainda com problemas aqui! :(

Consegui resolver o problema trocando o ListView pelo ListView.builder no initial_screen

class InitialScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: Container(),
        title: const Text('Tarefas'),
      ),
      body: Consumer<TaskInherited>(
        builder: (context, taskInherited, child) {
          return ListView.builder(
            itemCount: taskInherited.taskList.length,
            itemBuilder: (context, index){
            return taskInherited.taskList[index];
          });
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (contextNew) => FormScreen(taskContext: context),
            ),
          );
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}