ViewModel et initialisation

Hello les développeurs !

J’ai développé une app Flutter l’année dernière, puis je l’ai laissé de coté et j’ai perdu quelques notions depuis.
J’aimerai donc reprendre les choses en main et repartir sur de bonnes bases.

L’app que j’ai développé utilise déjà l’architecture MVVM avec le package « stacked » (stacked | Flutter Package), mais j’aimerai m’en libérer. J’ai donc regardé la formation PurpleGiraffe avec ViewModel qui m’a tout de suite parlé… Sauf pour un point.

Lorsqu’on navigue d’un écran vers un autre, je souhaite charger des infos complémentaires pour le nouvel écran.
Je ne souhaite pas le faire dans le routeur, car je ne souhaite pas bloquer la navigation. J’afficherai un chargement sur le nouvel écran pour les infos manquantes.

Hors MVVM, j’ai l’habitude de faire ça dans un initState dans un StatefullWidget.
En MVVM avec Stacked, j’avais la propriété onModelReady pour appeler ma méthode init dans mon ViewModel.

Mais ici, je ne sais pas trop comment faire :sweat_smile:

Voilà ce que j’ai tenté, qui fonctionne, sans savoir si c’est la bonne façon de faire (je vous ai dit j’ai perdu quelques notions !)

class HomeScreenViewModel extends ChangeNotifier {
  bool _isLoading = true;
  bool get isLoading => _isLoading;

  HomeScreenViewModel() {
    init();
  }

  init() async {
    await Future.delayed(const Duration(seconds: 2));
    _isLoading = false;
    notifyListeners();
  }
}
import 'package:flutter/material.dart';

import 'home_screen_viewmodel.dart';

class HomeScreen extends StatelessWidget {
  final HomeScreenViewModel _viewModel;
  const HomeScreen(this._viewModel, {super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: AnimatedBuilder(
          animation: _viewModel,
          builder: (context, child) {
            return Center(
                child: _viewModel.isLoading
                    ? const CircularProgressIndicator()
                    : Text('Home Screen');
          }),
    );
  }
}

L’autre solution qui me semble plus propre mais moins lisible est de passer par FutureBuilder.
Sinon, il reste simplement à le convertir en StatefullWidget et le faire dans le initState.

Qu’en pensez-vous ?

Merci d’avance !

Hello @DimZeta,

J’ai utilisé cette approche dans certaines vues, mais actuellement j’ai tendance à plutôt utiliser des FutureBuilder dans le cas des données asynchrones, généralement imbriquées dans des AnimatedBuilder, cela permet de gérer l’absence de data et au besoin les erreurs.

Dans tous les cas j’utilise des Viewmodel pour les données, et si j’ai des vues trop complexes j’utilise des Stateful mais uniquement pour gérer l’affichage.

expl de vue : (ici j’ai utilisé un Stateful pour gérer les animations)

      AnimatedBuilder(
        animation: widget._viewmodel,
        builder: (context, _) {
          return Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Padding(padding: EdgeInsets.only(top: 15)),
              FutureBuilder(
                future: widget._viewmodel.commune,
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    return IconLineCard(
                      icon: Icons.location_pin,
                      color: (snapshot.data?.nouvelleCommune ?? "").isNotEmpty
                          ? Colors.grey
                          : Colors.green,
                      actionIcon: Icons.navigation,
                      onTapActionIcon: widget._viewmodel.onTapNavigation,
                      child: Text("Code postal : ${snapshot.data?.codePostal}"),
                    ).animate(target: rebuild ? 0 : 1).fade();
                  }
                  return Container(
                    height: 0,
                  );
                },
              ),
              FutureBuilder(...

expl de Viewmodel

class CommunePageViewmodel extends ChangeNotifier {
  CommunePageViewmodel(this.identifiant);

  final String identifiant;

  Future<Commune?> get commune async =>
      await MyDatabase.commune.get(identifiant);
...

Merci @isanforc pour ta réponse, ça m’a bien aidé!

En effet je pense que l’utilisation de FutureBuilder est plus propre, en tout ças ça a bien fonctionné pour mon soucis :grin:

Je trouve le code un peu moins « beau » que ce que j’ai l’habitude de faire dans d’autres langages, mais c’est une question d’habitude je pense…

Encore merci, je vais partir sur cette manière de faire :+1: