skip to Main Content

I can’t do what I want, and above all, I don’t understand the mechanics.
In fact, I want to make a setState of a widget from another widget.

To explain my situation, I have a first AdministrationScreen widget that returns a Scaffold, the body containing a second widget (AdminWinTypesTab widget) and a Floating Action Button.

enter image description here

The Floating Action Button opens a modal bottom whose content is the ModalBottomContent widget.
The latter contains a column with the title and another widget, AdminWinTypesColumn.

enter image description here

What I want to do is a setState of my list (AdminWinTypesTab widget) once I commit and the function that changes my data is executed.

I put the code below, hoping that it is not too indigestible.

import 'package:flutter/material.dart';

import '../datas/datas.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test Refresh State',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const AdministrationScreen(),
    );
  }
}

class AdministrationScreen extends StatefulWidget {
  const AdministrationScreen({super.key});
  @override
  State<AdministrationScreen> createState() => _AdministrationScreenState();
}

class _AdministrationScreenState extends State<AdministrationScreen>
    with SingleTickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white,
        title: const Text('Test'),
      ),
      body: const AdminWinTypesTab(),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showModalBottomSheet(
            isScrollControlled: true,
            context: context,
            builder: (BuildContext context) {
              return SizedBox(
                height: MediaQuery.of(context).size.height / 1.75,
                child: const Scaffold(
                  body: SingleChildScrollView(
                    child: SizedBox(
                      child: Padding(
                        padding: EdgeInsets.all(20.0),
                        child: ModalBottomSheetContent(
                          add: true,
                        ),
                      ),
                    ),
                  ),
                ),
              );
            },
          );
        },
        backgroundColor: Colors.green,
        child: const Icon(
          Icons.add,
        ),
      ),
    );
  }
}

class ModalBottomSheetContent extends StatefulWidget {
  final bool add;
  final String? winType;

  const ModalBottomSheetContent({
    super.key,
    required this.add,
    this.winType,
  });

  @override
  State<ModalBottomSheetContent> createState() =>
      _ModalBottomSheetContentState();
}

class _ModalBottomSheetContentState extends State<ModalBottomSheetContent> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          '${widget.add ? 'Ajouter' : 'Modifier'} dans les types de gain',
          style: const TextStyle(
            fontSize: 30,
            fontWeight: FontWeight.bold,
          ),
          textAlign: TextAlign.center,
        ),
        const SizedBox(
          height: 10,
        ),
        AdminWinTypesColumn(add: widget.add, winType: widget.winType),
      ],
    );
  }
}

class AdminWinTypesColumn extends StatefulWidget {
  final bool add;
  final String? winType;

  const AdminWinTypesColumn({
    super.key,
    required this.add,
    this.winType,
  });

  @override
  State<AdminWinTypesColumn> createState() => _AdminWinTypesColumnState();
}

class _AdminWinTypesColumnState extends State<AdminWinTypesColumn> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final TextEditingController _nameController = TextEditingController();
  int? indexToModify;

  void _submit(bool add, int? indexToModify, String? winTypeToModify) async {
    if (_formKey.currentState!.validate()) {
      String action;
      if (add) {
        action = 'add';
      } else {
        action = 'modify';
      }

      modifyWinType(winTypeToModify!, action, indexToModify);

      if (context.mounted) {
        Navigator.of(context).pop();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(action),
            backgroundColor: Colors.green,
          ),
        );
      }
    }
    setState(() {});
  }

  @override
  void initState() {
    // Récupération des informations si modification
    if (!widget.add) {
      _nameController.text = widget.winType!;
      indexToModify = winTypes.indexOf(widget.winType!);
    }
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      child: Column(
        children: [
          Form(
            key: _formKey,
            child: Column(
              children: [
                Row(
                  children: [
                    const SizedBox(
                      width: 100,
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            'Type de gain',
                          ),
                          Text(
                            '(au singulier) :',
                          ),
                        ],
                      ),
                    ),
                    Flexible(
                      child: TextFormField(
                        controller: _nameController,
                        validator: (value) {
                          if (value == null || value.trim().isEmpty) {
                            return 'Entre le nom';
                          }
                          if (value == widget.winType) {
                            return 'Le nom est inchangé';
                          }
                          return null;
                        },
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
          const SizedBox(height: 30),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                  textStyle: const TextStyle(color: Colors.white),
                ),
                child: const Text('Annuler'),
              ),
              ElevatedButton(
                onPressed: () {
                  int? indexTomodify = widget.add ? null : indexToModify;
                  String? winTypeToModify = _nameController.text.trim();
                  _submit(widget.add, indexTomodify, winTypeToModify);
                },
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.green,
                  textStyle: const TextStyle(color: Colors.black),
                ),
                child: const Text('Valider'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class AdminWinTypesTab extends StatefulWidget {
  const AdminWinTypesTab({
    super.key,
  });

  @override
  State<AdminWinTypesTab> createState() => _AdminWinTypesTabState();
}

class _AdminWinTypesTabState extends State<AdminWinTypesTab> {
  Future<void> modifyWinTypesAndRefresh(
      String winType, String action, int indexToModify) async {
    Color? snackBarColor;
    String? snackBarMessage;

    await modifyWinType(winType, action, indexToModify);
    if (action == 'delete') {
      snackBarColor = Colors.red;
      snackBarMessage = 'Type de gain "$winType" supprimé';
    }
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            snackBarMessage!,
          ),
          backgroundColor: snackBarColor,
        ),
      );
    }
    setState(() {});
  }

  void refreshWinTypesTab() {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<String>?>(
      future: getWinTypes(),
      builder: (context, snapshot) {
        if (snapshot.hasData && snapshot.data != null) {
          List<String> fetchedWinTypes = snapshot.data!;
          return ListView.builder(
            itemCount: fetchedWinTypes.length,
            itemBuilder: (context, index) {
              String winType = fetchedWinTypes[index];
              return ListTile(
                tileColor: index.isOdd ? null : Colors.grey.shade300,
                title: Text(
                  winType,
                  style: const TextStyle(
                      fontSize: 20, fontWeight: FontWeight.bold),
                ),
                trailing: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    IconButton(
                        onPressed: () {
                          showModalBottomSheet(
                            isScrollControlled: true,
                            context: context,
                            builder: (BuildContext context) {
                              return SizedBox(
                                height:
                                    MediaQuery.of(context).size.height / 1.75,
                                child: Scaffold(
                                  body: SingleChildScrollView(
                                    child: Padding(
                                      padding: const EdgeInsets.all(20.0),
                                      child: ModalBottomSheetContent(
                                        add: false,
                                        winType: winType,
                                      ),
                                    ),
                                  ),
                                ),
                              );
                            },
                          );
                        },
                        icon: const Icon(Icons.edit, color: Colors.green)),
                    IconButton(
                      onPressed: () async {
                        await showDialog(
                          context: context,
                          builder: (context) {
                            return AlertDialog(
                              title: Column(
                                children: [
                                  const Icon(
                                    Icons.dangerous,
                                    color: Colors.red,
                                  ),
                                  Text(
                                    'Supprimer le type de gain "$winType" ?',
                                    style: const TextStyle(
                                      color: Colors.red,
                                      fontSize: 20,
                                      fontWeight: FontWeight.bold,
                                    ),
                                  ),
                                ],
                              ),
                              actions: <Widget>[
                                TextButton(
                                  onPressed: () {
                                    Navigator.of(context).pop();
                                  },
                                  child: const Text(
                                    'Annuler',
                                    style: TextStyle(
                                      color: Colors.black,
                                      fontSize: 16,
                                      fontWeight: FontWeight.bold,
                                    ),
                                  ),
                                ),
                                TextButton(
                                  onPressed: () {
                                    modifyWinTypesAndRefresh(
                                        winType, 'delete', index);
                                    Navigator.of(context).pop();
                                  },
                                  child: const Text(
                                    'Supprimer',
                                    style: TextStyle(
                                      color: Colors.red,
                                      fontSize: 16,
                                      fontWeight: FontWeight.bold,
                                    ),
                                  ),
                                ),
                              ],
                            );
                          },
                        );
                      },
                      icon: const Icon(
                        Icons.delete_forever,
                        color: Colors.red,
                      ),
                    ),
                  ],
                ),
              );
            },
          );
        } else if (snapshot.hasError) {
          return Center(child: Text('Erreur: ${snapshot.error}'));
        } else {
          return const Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    );
  }
}

Thanks a lot for your help.

2

Answers


  1. Looking at your code, I notice a few things.
    First of all, I notice you use classes for everything.
    In Dart and Flutter, you don’t need to define classes for all of your elements.
    Some build methods can be a simple function. disconnected from all the classes, meaning that different objects can (re)use it for their build processes.

    In the same way, if you have a main class, you can track all of your objects in just one place, and all other elements within the build tree, have direct access to its keys and methods.

    These methods can be used to open your modal widget while using as parameters the parent context for the state change, and a callback action in case you need it.

    Widget openModal(BuildContext parentContext){
       ... <your widget build method here>
    }
    

    Another method you can use is the Callback action.
    When you open the modal, you can pass as arguments the callback actions for the main widget like this

    Widget openModal(Function editAction, Function deleteAction){
       return     // you return your modal
       ...
       <editButton> 
       onTap: () => editAction();
       ...
       <deleteButton> 
       onTap: () => deleteAction();
       ...
    }
    
    Login or Signup to reply.
  2. You can define a function in your parent widget AdminWinTypesTab, and pass it to your child widgets (Modal Bottom Sheet and AdminWinTypesColumn), and call it after your data is changed. More complex but efficient way is to use state manager like Provider package. State managers handle data changes for you, e.g. when you modify your data you don’t need to worry about refreshing widgets that depend on this data.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search