Widget, notifier et class Aux secours à l'aide

Bonjour, j’ai un petit soucis de mise en oeuvre dans une appli flutter.

j’ai un widget qui est un écran de paramètres (class Settings), qui permet de modifier le nombre de volées (tir à l’arc) en appelant un autre widget CounterView qui a un bouton - le texte et un bouton +.
image

Je communique la valeur initiale (issue d’une classe discipline, propriété nombre de volées) via un ChangeNotifierProvider, que je récupère dans CounterView, qui lui même met à jour ce notifier sur clique des boutons - et +.
J’aimerais pouvoir modifier la propriété nombre de volées de ma class discipline, mais je ne sais pas comment faire… la class consumer() est utilisée dans les widget, mais je ne sais pas si c’est ce que je dois utiliser, où et comment…
Mon but est de modifier mon objet discipline.nombreVolees avec le résultat du widget counterView, sans manipuler l’objet discipline.nombreVolees dans cette class, afin que je puisse réutiliser le widget avec une autre valeur comme nombreFleches, ou avec une autre class.
Merci

Voici mon code :

ma class Discipline :

    class Discipline {
      String title;
      String imageAsset;
      String description;
      int nombreVolees;
      int nombreFleches;

      Discipline(this.title, this.imageAsset, this.description, this.nombreVolees,
          this.nombreFleches);
    }

ma class Settings :

    import 'package:clicker/CounterView.dart';
    import 'package:clicker/nombreChangeNotifier.dart';
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    import 'Discipline.dart';

    class SettingsDiscipline extends StatefulWidget {
      **final Discipline discipline;**
    **  SettingsDiscipline({Key? key, required this.discipline}) : super(key: key);**

      @override
      State<StatefulWidget> createState() {
        return SettingsDisciplineState();
      }
    }

    class SettingsDisciplineState extends State<SettingsDiscipline> {
      @override
      Widget build(BuildContext context) {
        return **ChangeNotifierProvider**(
          create: (context) => **NombreChangeNotifier(widget.discipline.nombreVolees),**
          child: Scaffold(
            appBar: AppBar(
              title: Text('Paramètres'),
              foregroundColor: Colors.white,
            ),
            body: Column(
              children: <Widget>[
                Row(
                  children: [
                    Container(
                      margin: EdgeInsets.all(20.0),
                      child: Text("Nombre de volées : ",
                          style: TextStyle(
                            fontSize: 20,
                          )),
                    ),
                    Container(
                      margin: EdgeInsets.all(20.0),
                      child: **CounterView(),**
                    ),
                  ],
                ),
                Row(
                  children: [
                    Container(
                      margin: EdgeInsets.all(20.0),
                      child: Text("Nombre de flèches : ",
                          style: TextStyle(
                            fontSize: 20,
                          )),
                    ),
                    Container(
                      margin: EdgeInsets.all(20.0),
                      child: Text(widget.discipline.nombreFleches.toString(),
                          style: TextStyle(
                            fontSize: 20,
                          )),
                    ),
                  ],
                )
              ],
            ),
          ),
        );
        // );
      }

      Column _buildButtonColumn(Color color, IconData icon, String label) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
                padding: const EdgeInsets.only(bottom: 8),
                child: Icon(icon, color: color)),
            Text(label,
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.w400,
                  color: color,
                ))
          ],
        );
      }
    }

Le widget counterView :

import 'package:flutter/material.dart';
import 'package:clicker/Discipline.dart';
import 'package:clicker/palette.dart';
import 'package:provider/provider.dart';
import 'nombreChangeNotifier.dart';

class CounterView extends StatefulWidget {
  // final Discipline discipline;

//  CounterView({Key? key, required this.discipline}) : super(key: key);

  @override
  _CounterViewState createState() => _CounterViewState();
}

class _CounterViewState extends State<CounterView> {
  late int _minNumber;
  late int _nombreModifie;

  @override
  void initState() {
    _minNumber = 1;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    **NombreChangeNotifier _notifier = Provider.of<NombreChangeNotifier>(context);**
    **_nombreModifie = _notifier.nombreModifie;**
    return Container(
      padding: EdgeInsets.zero,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(50),
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          _createIncrementDicrementButton(
              Icons.remove, () => _dicrement(_notifier)),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text(_nombreModifie.toString(),
                style: TextStyle(
                  fontSize: 20,
                )),
          ),
          _createIncrementDicrementButton(
              Icons.add, () => _increment(_notifier)),
        ],
      ),
    );
  }

  void _increment(NombreChangeNotifier _notifier) {
    setState(() {
      //widget.discipline.nombreVolees++;
      _nombreModifie++;
      _notifier.nombreModifie = _nombreModifie;
    });
  }

  void _dicrement(NombreChangeNotifier _notifier) {
    setState(() {
      if (_nombreModifie > _minNumber) {
        //widget.discipline.nombreVolees--;
        _nombreModifie--;
        _notifier.nombreModifie = _nombreModifie;
      }
    });
  }

  Widget _createIncrementDicrementButton(IconData icon, Function onPressed) {
    return RawMaterialButton(
      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
      constraints: BoxConstraints(minWidth: 28.0, minHeight: 28.0),
      onPressed: () => onPressed(),
      elevation: 3.0,
      fillColor: Palette.bleuArchers,
      child: Icon(
        icon,
        color: Colors.white,
        size: 16.0,
      ),
      shape: CircleBorder(),
    );
  }
}

Mon notifier :

import 'package:flutter/foundation.dart';

class NombreChangeNotifier with ChangeNotifier {
  int _nombreModifie;

  NombreChangeNotifier(this._nombreModifie);

  int get nombreModifie => _nombreModifie;

  set nombreModifie(int nombreModifie) {
    _nombreModifie = nombreModifie;
    notifyListeners();
  }
}

Salut,

Je ne pense pas que le ChangeNotifier soit le moyen le plus intéressant si c’est seulement pour des paramètre assez basique. À la place j’utiliserai le package shared_preference pour stocker ce genre de donnée directement sur le téléphone.

Ensuite vous pouvez faire une écriture avec un
await prefs.setInt('counter', 10);

Puis un simple
final int? counter = prefs.getInt('counter');

Pour mettre en forme le code sur le forum, vous avez la balise :
Screenshot 2022-07-12 at 11.51.04 AM

Bonjour, merci pour votre réponse.
Je viens de l’AS400, puis dev avec AGL maison (Adélia) qui n’est pas « objet » du coup de galère un peu.

Si je comprends bien, je peux créer une instance de ma classe discipline avec les valeurs par défaut (nombrevolees), et lors que l’utilisateur modifie la valeur via le widget CounterView, je stockes par un share_preference sur le téléphone, c’est bien cela ?

par contre ce que j’ai du mal à voir, c’est à quelle endroit je dois faire ce share preference.

En effet, dans mon Settings.dart, j’appelle mon widget CounterView comme ceci :

Container(
                  margin: EdgeInsets.all(20.0),
                  child: CounterView(),
                ),

Du coup, je vais donc passer « widget.discipline.nombreVolees » en paramètre de mon widget CounterView, afin de ne plus utiliser les notifier :

    Container(
                      margin: EdgeInsets.all(20.0),
                      child: CounterView(widget.discipline.nombreVolees),
                    ),

mais comment je fais ensuite pour alimenter le share_preference ? je ne peux à priori pas mettre de code dans le container, et je ne veux pas le faire dans le CounterView.dart, afin qu’il reste autonome.
C’est vraiment cette interaction qui ne gène, je ne visualise pas (mon cerveau de développeur séquentiel m’en empêche). Mon but, était de remettre à jour mon instance de class discipline.nombreVolees.
Ca marchait bien quand je passais l’instance de class complète en paramètre de CounterView, je mettais à jour discipline.nombreVolees sur les actions de mon widget, mais du coup ce widget était exclusif sur cette valeur, ce qui me gênait.

Merci pour votre aide nécessaire
Sébastien

C’est à peu près ça, l’idée serait de vérifier au lancement de la page ou l’application si une donnée compteur est déjà présente et si c’est le cas de la charger dans votre classe sinon d’initialiser le shared preference et d’écrire une valeur par défaut…
Pour la modification de cette valeur, je ferais bien un bouton sauvegarder dans les paramètres qui stockera la nouvelle donnée dans shared preference une fois cliquer dessus.

Pour finir votre classe se contentera de seulement lire les données compteur du shared preference et la partie paramètre servira à la modification de cette variable.
Vous pourrez réutiliser la valeur compteur dans toute votre application.

Plus besoin de passer des informations à votre classe comme cela avec le shared preference, vous faites appel à la donnée via la fonction get du package ex: final int? counter = prefs.getInt(‹ counter ›);

Example d’utilisation:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'SharedPreferences Demo',
      home: SharedPreferencesDemo(),
    );
  }
}

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

  @override
  SharedPreferencesDemoState createState() => SharedPreferencesDemoState();
}

class SharedPreferencesDemoState extends State<SharedPreferencesDemo> {
  final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
  late Future<int> _counter;

  Future<void> _incrementCounter() async {
    final SharedPreferences prefs = await _prefs;
    final int counter = (prefs.getInt('counter') ?? 0) + 1;

    setState(() {
      _counter = prefs.setInt('counter', counter).then((bool success) {
        return counter;
      });
    });
  }

  @override
  void initState() {
    super.initState();
    _counter = _prefs.then((SharedPreferences prefs) {
      return prefs.getInt('counter') ?? 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('SharedPreferences Demo'),
      ),
      body: Center(
          child: FutureBuilder<int>(
              future: _counter,
              builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                switch (snapshot.connectionState) {
                  case ConnectionState.waiting:
                    return const CircularProgressIndicator();
                  default:
                    if (snapshot.hasError) {
                      return Text('Error: ${snapshot.error}');
                    } else {
                      return Text(
                        'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n'
                        'This should persist across restarts.',
                      );
                    }
                }
              })),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Si je ne passe plus de paramètre à mon widget CounterView, cela veut dire que c’est dans CounterView que je dois alimenter le ‹ counter › ? du coup mon widget n’est plus autonome et non réutilisable, d’autant que que j’ai plusieurs disciplines avec des nombreVolees différents.

Dans l’exemple d’utilisation, si on remplace tout le code du child; du boby: par child: CounterView(),

qui ferait (je passe les déclarations de class):

Center(
          child: FutureBuilder<int>(
              future: _counter,
              builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                switch (snapshot.connectionState) {
                  case ConnectionState.waiting:
                    return const CircularProgressIndicator();
                  default:
                    if (snapshot.hasError) {
                      return Text('Error: ${snapshot.error}');
                    } else {
                      return Text(
                        'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n'
                        'This should persist across restarts.',
                      );
                    }
                }
              })),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );

Comment écrire afin que le CounterView se contente de faire +1 à afficher, et que ce soit le code appelant qui se charge de mettre à jour via _incrementCounter ??

j’imagine que ma logique n’est pas bonne, je garde mes vieux démons de programmes A qui appellent un autre programme B et qui reprend la main quand B se termine… c’est toute une culture à refaire.

Je comprends mieux là où vous bloquez, au niveau de la séparation de la logique de l’affichage. Actuellement le CounterView est un widget modulable qui contient aussi sa propre logique et si j’ai bien compris est utilisé pour d’autres paramètres.

Il est donc important de séparer totalement la logique de la partie affichage, dans ce cas là oui il sera utile de faire passer une information pour dire au widget qu’il s’agit du nombre de volée et des choses à effectuer dans ce cas de figure.

Le moyen le plus simple reste de faire passer la fonction à enclencher en argument(je préfère paramètre) au widget CounterView. Ces fonctions seraient codés dans la class Settings et envoyer ensuite aux widgets CounterView. Dans chaque fonction il y aurait un appel de sharedPreference, lecture et écriture.

1 J'aime

Merci pour vos réponses intéressantes.
Auriez-vous un exemple de code de passage de fonction en paramètre ? je cherche sur le net, mais je n’ai pas trouvé de résultat concluant.
merci
Sébastien

Bien sûr, si je reprends le widget de ci-dessus, il y a bien un passage de fonction avec l’argument onPressed, sauf qu’il s’agit d’une fonction et non d’une classe pour le moment.

Voici ce que donnerait cette fonction _createIncrementDicrementButton en class (privilégiez les noms plus court):

import 'package:flutter/material.dart';

class CreateButton {

  final IconData icon;

  final void Function()? onPressed;

  final Color? fillColor;

  CreateButton({required this.icon, required this.onPressed, this.fillColor});

  build() {

    return RawMaterialButton(

      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,

      constraints: BoxConstraints(minWidth: 28.0, minHeight: 28.0),

      onPressed: onPressed,

      elevation: 3.0,

      fillColor: fillColor ?? Colors.white,

      child: Icon(

        icon,

        color: Colors.white,

        size: 16.0,

      ),

      shape: CircleBorder(),

    );

  }

}

Dans l’exemple, je crée mes variables dans ma classe qui correspondent aux arguments de la fonction.
Ensuite je crée mon constructeur de classe qui permettra d’appeler celle-ci et faire passer les paramètres.
Privilégiez les paramètres aux arguments si vous avez beaucoup d’informations à passer.
Pour finir on appel la classe CreateButton et instancie l’objet:
final _button = CreateButton(icon:IconDataSomething, onPressed:(){}); _button.build();

Flutter est essentiellement constitué de classe, d’ailleurs tous les widgets sont aussi des classes.

Bonjour et merci
Je regarderai cela de prêt à mon retour.
J’ai encore bien du mal avec les variables, je vois pas encore bien la différence entre une déclaration simple comme int variable et les déclarations avec les mots clés Var. Final, Future…j’en perd mon latin

C’est ce que j’apprécie dans Dart, ce côté fortement typé qui permet d’accélérer la compilation et faire du hot reload. Après oui cela prend un moment avant de comprendre les termes, mais cela apporte beaucoup de protection et une meilleure lecture.
Pour les déclarations je vous conseille de suivre une formation, plus simple à expliquer avec des exemples.