Bien débuter avec Directus API

Hello !

Ayant suivi les cours de @mbritto et la conf de @jmjoary, j’ai un peu honte de demander… mais j’ai un peu de mal à débuter, histoire de mettre le pieds à l’étrier !

Je cherche à insérer un protocole d’identification dans le cours sur l’architecture, histoire d’avancer pas à pas, mais comment récupère t-on le résultat de checkDirectusLogin ?

Future<DirectusLoginResult> checkDirectusLogin() async {
    return await _apiManager.loginDirectusUser("[email protected]", "admin");
  }

Merci beaucoup !

loginDirectusUser() te retourne un objet DirectusLoginResult.

enum DirectusLoginResultType { success, invalidCredentials, error }

class DirectusLoginResult {
  final DirectusLoginResultType type;
  final String? message;

  const DirectusLoginResult(this.type, {this.message});
}

on peut donc faire

final DirectusLoginResult loginResult = await _apiManager.loginDirectusUser("[email protected]", "admin");

if(loginResult.type == DirectusLoginResultType.success) {
     _connectedUser = await _apiManager.currentDirectusUser();
}

il faut donc commencer par regarder dans type si la connexion est un succès. Après tu peux utiliser Future<DirectusUser?> currentDirectusUser({String fields = "*"})pour récupérer l’utilisateur connecté.

Merci de ta réponse ! J’en profite, autre question très basique, pour déclarer le Directus API dans Delegate, la déclaration de variable ne suffit-elle pas ?

 final DirectusApiManager _apiManager = DirectusApiManager(
      baseURL: "http://127.0.0.1:8055", httpClient: Client());

en variable de classe ? Je suis certain de passer les bons ID (si username est bien l’email) et le retour est systématiquement : DirectusLoginResultType.invalidCredentials

En principe si. Tu arrives à te connecter en passant par l’application directus ?

Si, l’app Directus fonctionne à merveille, c’est dans l’app Flutter le souci

C’est bon finalement, me suis débrouillé, ça fonctionne ! Merci encore

Hello,

J’ai encore une petite question : dans le meetup il y a cette série de fonctions qui permettent de lister des messages (dans message_service)

Il n’y a donc pas besoin d’utiliser le directusItem ? et par ailleurs comment produit-on le lien avec les champs indiqués plus haut ?

désolé, ça fait 2 questions ! Merci

Dans la capture d’écran que tu indiques, l’objet Messages hérite de DirectusItem. Cela est indispensable pour que le paquet fonctionne correctement.

class Message extends DirectusItem {
  Message(super.rawReceivedData) ;
  Message.newItem() : super.newItem();
  @override
  String get endpointName => "messages";
}

Le service fournit de base l’ajout, la modification et la suppression dans la collection. Il faut la déclarer comme ceci

class MessageService extends DirectusService<Message> { // Préciser le type d'objet qui va être géré par ce service entre les chevrons
  MessageService({required DirectusApiManager apiManager})
      : super(
          apiManager: apiManager,
          typeName: 'messages', // Le nom de la collection
          fields: '*,user_created.*', // Les champs par défaut que tu souhaites récupérer.
        );

  @override
  Message fromDirectus(rawData) {
    return Message(rawData);
  }

ensuite lorsque tu appelles les différentes fonctions du paquet, il y a dans chaque cas, si je n’ai rien oublié, un paramètre field qui te permet de choisir les champs que tu souhaites récupérer.

Future<Iterable<Type>> findListOfItems<Type extends DirectusItem>(
      {required String name,
      Filter? filter,
      List<SortProperty>? sortBy,
      String fields = "*",
      int? limit,
      int? offset,
      required Type Function(dynamic json) createItemFunction})

n’hésite pas si tu as d’autres questions ou si mes explications ne sont pas assez claires.

J’ai commencé à ajouter de la doc texte, à la fois sur la page d’accueil, et aussi sous la forme de doc dans plusieurs fonctions.

1 « J'aime »

Merci beaucoup pour ta réponse complète @jmjoary. J’essaie de comprendre, surtout comment on enchaine avec le viewmodele et l’architecture générale.

  • on crée un service spécifique pour accéder facile aux fonctions d’édition, de sélection spécifique et de suppression ?

  • Pour simplement lire des paramètre, on passe simplement par le view modèle en appelant une fonction du type :

 void testFunction() {
    _apiDirectus.findListOfItems(name: 'mycollection', createItemFunction: allItem)
  }

  void allItem() {

  }

c’est bien ça ? mais que doit-on faire avec la fonction allItem ? J’imagine qu’elle retourne forcément une liste ?

  • on crée un service spécifique pour accéder facile aux fonctions d’édition, de sélection spécifique et de suppression ?

tout à fait DirectusService sert à cela.

dans l’exemple que tu mets, je suppose que nous sommes dans un objet qui hérite de DirectusService.

class MessageService extends DirectusService<Message> { // Préciser le type d'objet qui va être géré par ce service entre les chevrons
  MessageService({required DirectusApiManager apiManager})
      : super(
          apiManager: apiManager,
          typeName: 'messages', // Le nom de la collection
          fields: '*,user_created.*', // Les champs par défaut que tu souhaites récupérer.
        );

  @override
  Message fromDirectus(rawData) {
    return Message(rawData);
  }

lorsque je déclare mon service, je spécifie à plusieurs endroits le type d’objet qu’il va gérer (dans l’exemple, il s’agit de Message)

  • Lorsque je déclare que mon service hérite de DirectusService, note le Message entre les chevrons.
  • La méthode fromDirectus retourne également un objet de type Message

Maintenant regarde la signature de la fonction findListOfItems

Future<Iterable<Type>> findListOfItems<Type extends DirectusItem>(
      {required String name,
      Filter? filter,
      List<SortProperty>? sortBy,
      String fields = "*",
      int? limit,
      int? offset,
      required Type Function(dynamic json) createItemFunction})

Le paramètre createItemFunction attend une fonction qui va créer ton objet, donc tu peux lui passer la méthode fromDirectus de ton service. findListOfItemsva donc :

  1. envoyer la requête à ton serveur,

  2. récupérer la réponse

  3. créer les objets en utilisant la méthode fromDirectus

  4. te retourner un iterable qui contiendra les objets demandés.

Par exemple, j’ai cette fonction dans mon service qui me retourne tous les messages :

Future<List<Message>> getAllMessages() async {
    return List<Message>.from(await apiManager.findListOfItems(
        name: typeName,
        fields: fields,
        createItemFunction: fromDirectus,
        );
  }

Pour l’utiliser :

  1. Je crée un objet MessageService dans mon view model
  2. j’appelle mon fonction getAllMessages en enregistrant le résultat dans une variable.
final MessageService messageService = MessageService(apiManager: _apiManager);
Iterable<Message> allMessages = await messageService. getAllMessages();

Ma variable allMessages contiendra un tableau avec tous les objets Message de ma collection.

1 « J'aime »

Merci pour ton temps, j’arrive maintenant à afficher les premières infos !

Avec plaisir. Une fois que tu auras bien assimilé comment les objets interagissent entre eux, tu verras que le paquet fait gagner un temps de fou.

J’en suis convaincu, maintenant faut que je mette en pratique les apprentissages sur le traitement des retours, le traitement des maps pour l’affichage, pour dépasser l’étape du :

snapshot.data?.first.getValue(forKey: "monChamp")

et afficher des lignes complètes, mais ça c’est un autre sujet !

Merci encore du boulot et du partage. J’aimerais bien ensuite contribuer à une doc, ou à un retour d’expérience pour faciliter la prise en main pour d’autres.

Je connais avec une autre question, l’usage concret des différentes classes :

Dans mon service j’ai positionné :

class MusicService extends DirectusService<Music> {
  MusicService({required DirectusApiManager apiManager})
      : super(
          apiManager: apiManager,
          typeName: 'musics',
          fields: '*,user_created.*, title.*, album.*, years.*, artiste.*',
        );

  @override
  Music fromDirectus(rawData) {
    return Music(rawData);
  }

  Future<List<Music>> getAllMusics() async {
    try {
      return List<Music>.from(await apiManager.findListOfItems(
        name: typeName,
        fields: fields,
        createItemFunction: fromDirectus,
      ));
    } catch (exe) {
      print(exe);
      throw false;
    }
  }

Dans le view model :

class MusicViewModel extends IMusicViewModel {
  final DirectusApiManager _apiDirectus;
  final _musicService = MusicService;
  MusicViewModel(this._apiDirectus);

  @override
  Future<List<Music>> getMyMusic() async {
    final MusicService musicService = MusicService(apiManager: _apiDirectus);
    List<Music> allMusics = await musicService.getAllMusics();
    print(allMusics.elementAt(1).album);
    return allMusics;
  }

Je récupère bien les infos de Directus, comme me le présente le print()

Puis dans la view :

 int compteur() {
    Map _map = widget.musicViewModel.getMyMusic();
    return _map.length;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text("Music List")),
        body: Container(
            width: double.infinity,
            height: 500,
            child: ListView.builder(
              itemCount: compteur(),
              itemBuilder: (context, index) {
                return MusicLigne(
                  id: index,
                  musicList: widget.musicViewModel.getMyMusic(),
                );
              },
            ))
        );
  }
}

et pour finir le widget qui composera les items :

class MusicLigne extends StatelessWidget {
  final List<Music> musicList;
  final int id;
  // final String album;
  // final String artiste;
  // final int year;
  // final String titre;
  const MusicLigne({super.key, required this.musicList, required this.id});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: 80,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Expanded(
            flex: 5,
            child: Text(musicList.elementAt(id).title),
          ),
          Expanded(
            flex: 5,
            child: Text(musicList.elementAt(id).artist),
          ),
          Expanded(
            flex: 5,
            child: Text(musicList.elementAt(id).album),
          ),
          Expanded(
            flex: 5,
            child: Text(musicList.elementAt(id).year),
          ),
        ],
      ),
    );
  }
}

Je n’ai aucune erreur dans VSC, mais toujours sur l’exe :

The following _TypeError was thrown building MusicListView(dirty, state: _MusicListViewState#fe713):
type 'Future<List<Music>>' is not a subtype of type 'Map<dynamic, dynamic>'

Je suis dessus depuis un moment, mais je n’arrive pas à comprendre ce que je convertit mal, puisque tout est du type List…

Merci !

Je pense que le problème est dans ta vue.

 int compteur() {
    Map _map = widget.musicViewModel.getMyMusic();
    return _map.length;
  }

tu déclares _map comme un objet Map alors que getMyMuscic retourne un objet List.

Essaye de le remplacer par ceci :

 int compteur() {
    List<Music> _musicList = widget.musicViewModel.getMyMusic();
    return _musicList.length;
  }

Maintenant que cela fonctionne, je poste ma solution, au cas où…

Dans la view :

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text("Music List")),
        body: SizedBox(
          width: double.infinity,
          height: 500,
          child: FutureBuilder<List<Music>>(
              future: widget.musicViewModel.getMyMusic(),
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.done) {
                  if (snapshot.hasError) {
                    return const Center(
                      child: Text('An error has occurred!'),
                    );
                  } else {
                    final data = snapshot.data;
                    return Center(
                        child: ListView.builder(
                      itemCount: data!.length,
                      itemBuilder: (context, index) {
                        return MusicLigne(
                          id: index,
                          musicList: snapshot.data!,
                        );
                      },
                    ));
                  }
                } else {
                  return const Center(child: CircularProgressIndicator());
                }
              }),
        ));
  }
}