Comment importer une dépendance spécifique à une plateforme dans Flutter / Dart? (Combinez le Web avec Android / iOS)

9

J'utilise shared_preferencesdans mon application Flutter pour iOS et Android. Sur le Web, j'utilise lehttp:dart dépendance ( window.localStorage) elle-même. Depuis que Flutter pour le Web a été fusionné dans le référentiel Flutter, je souhaite créer une solution multiplateforme.

Cela signifie que je dois importer deux API distinctes. Cela ne semble pas encore très bien pris en charge dans Dart, mais c'est ce que j'ai fait:

import 'package:some_project/stub/preference_utils_stub.dart'
    if (dart.library.html) 'dart:html'
    if (dart.library.io) 'package:shared_preferences/shared_preferences.dart';

Dans mon preference_utils_stub.dartfichier, j'ai implémenté toutes les classes / variables qui doivent être visibles lors de la compilation:

Window window;

class SharedPreferences {
  static Future<SharedPreferences> get getInstance async {}
  setString(String key, String value) {}
  getString(String key) {}
}

class Window {
  Map<String, String> localStorage;
}

Cela supprime toutes les erreurs avant la compilation. Maintenant, j'ai implémenté une méthode qui vérifie si l'application utilise le Web ou non:

static Future<String> getString(String key) async {
    if (kIsWeb) {
       return window.localStorage[key];
    }
    SharedPreferences preferences = await SharedPreferences.getInstance;
    return preferences.getString(key);
}

Cependant, cela donne beaucoup d'erreurs:

lib/utils/preference_utils.dart:13:7: Error: Getter not found:
'window'.
      window.localStorage[key] = value;
      ^^^^^^ lib/utils/preference_utils.dart:15:39: Error: A value of type 'Future<SharedPreferences> Function()' can't be assigned to a
variable of type 'SharedPreferences'.
 - 'Future' is from 'dart:async'.
 - 'SharedPreferences' is from 'package:shared_preferences/shared_preferences.dart'
('../../flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.5.4+3/lib/shared_preferences.dart').
      SharedPreferences preferences = await SharedPreferences.getInstance;
                                      ^ lib/utils/preference_utils.dart:22:14: Error: Getter not found:
'window'.
      return window.localStorage[key];

Etc. Comment peut-on utiliser différentes méthodes / classes selon la plateforme sans ces erreurs? Notez que j'utilise plus de dépendances de cette façon, pas seulement des préférences. Merci!

Giovanni
la source
À ma connaissance limitée, vous ne devriez pas avoir à la fois les dépendances localstorageet shared preferencesdans la même méthode ou classe. Cela signifie que le compilateur ne peut pas secouer l'arborescence de ces dépendances. Idéalement, l'importation devrait masquer ces implémentations. Je vais essayer de trouver un exemple de mise en œuvre clair.
Abhilash Chandran
Vous pouvez utiliser le kIsWeb booléen global qui peut vous dire si l'application a été compilée pour fonctionner sur le Web. Documentation: api.flutter.dev/flutter/foundation/kIsWeb-constant.html if (kIsWeb) {// en cours d'exécution sur le web! initialiser le web db} else {// utiliser les préférences partagées}
Shamik Chodankar

Réponses:

20

Voici mon approche de votre problème. Ceci est basé sur les implémentations du httppackage comme ici .

L'idée centrale est la suivante.

  1. Créez une classe abstraite pour définir les méthodes que vous devrez utiliser.
  2. Créez des implémentations spécifiques à webet des androiddépendances qui étendent cette classe abstraite.
  3. Créez un stub qui expose une méthode pour renvoyer l'instance de cette implémentation abstraite. C'est seulement pour garder l'outil d'analyse de fléchettes heureux.
  4. Dans la classe abstraite, importez ce fichier de raccord avec les importations conditionnelles spécifiques à mobileet web. Ensuite, dans son constructeur d'usine, retournez l'instance de l'implémentation spécifique. Cela sera géré automatiquement par importation conditionnelle s'il est écrit correctement.

Étape 1 et 4:

import 'key_finder_stub.dart'
    // ignore: uri_does_not_exist
    if (dart.library.io) 'package:flutter_conditional_dependencies_example/storage/shared_pref_key_finder.dart'
    // ignore: uri_does_not_exist
    if (dart.library.html) 'package:flutter_conditional_dependencies_example/storage/web_key_finder.dart';

abstract class KeyFinder {

  // some generic methods to be exposed.

  /// returns a value based on the key
  String getKeyValue(String key) {
    return "I am from the interface";
  }

  /// stores a key value pair in the respective storage.
  void setKeyValue(String key, String value) {}

  /// factory constructor to return the correct implementation.
  factory KeyFinder() => getKeyFinder();
}

Étape 2.1: Recherche de clé Web

import 'dart:html';

import 'package:flutter_conditional_dependencies_example/storage/key_finder_interface.dart';

Window windowLoc;

class WebKeyFinder implements KeyFinder {

  WebKeyFinder() {
    windowLoc = window;
    print("Widnow is initialized");
    // storing something initially just to make sure it works. :)
    windowLoc.localStorage["MyKey"] = "I am from web local storage";
  }

  String getKeyValue(String key) {
    return windowLoc.localStorage[key];
  }

  void setKeyValue(String key, String value) {
    windowLoc.localStorage[key] = value;
  }  
}

KeyFinder getKeyFinder() => WebKeyFinder();

Étape 2.2: Recherche de clé mobile

import 'package:flutter_conditional_dependencies_example/storage/key_finder_interface.dart';
import 'package:shared_preferences/shared_preferences.dart';

class SharedPrefKeyFinder implements KeyFinder {
  SharedPreferences _instance;

  SharedPrefKeyFinder() {
    SharedPreferences.getInstance().then((SharedPreferences instance) {
      _instance = instance;
      // Just initializing something so that it can be fetched.
      _instance.setString("MyKey", "I am from Shared Preference");
    });
  }

  String getKeyValue(String key) {
    return _instance?.getString(key) ??
        'shared preference is not yet initialized';
  }

  void setKeyValue(String key, String value) {
    _instance?.setString(key, value);
  }

}

KeyFinder getKeyFinder() => SharedPrefKeyFinder();

Étape 3:

import 'key_finder_interface.dart';

KeyFinder getKeyFinder() => throw UnsupportedError(
    'Cannot create a keyfinder without the packages dart:html or package:shared_preferences');

Ensuite, dans votre main.dartutilisation, la KeyFinderclasse abstraite comme si c'était une implémentation générique. C'est un peu comme un modèle d'adaptateur .

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_conditional_dependencies_example/storage/key_finder_interface.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    KeyFinder keyFinder = KeyFinder();
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SafeArea(
        child: KeyValueWidget(
          keyFinder: keyFinder,
        ),
      ),
    );
  }
}

class KeyValueWidget extends StatefulWidget {
  final KeyFinder keyFinder;

  KeyValueWidget({this.keyFinder});
  @override
  _KeyValueWidgetState createState() => _KeyValueWidgetState();
}

class _KeyValueWidgetState extends State<KeyValueWidget> {
  String key = "MyKey";
  TextEditingController _keyTextController = TextEditingController();
  TextEditingController _valueTextController = TextEditingController();
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Container(
        width: 200.0,
        child: Column(
          children: <Widget>[
            Expanded(
              child: Text(
                '$key / ${widget.keyFinder.getKeyValue(key)}',
                style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
              ),
            ),
            Expanded(
              child: TextFormField(
                decoration: InputDecoration(
                  labelText: "Key",
                  border: OutlineInputBorder(),
                ),
                controller: _keyTextController,
              ),
            ),
            Expanded(
              child: TextFormField(
                decoration: InputDecoration(
                  labelText: "Value",
                  border: OutlineInputBorder(),
                ),
                controller: _valueTextController,
              ),
            ),
            RaisedButton(
              child: Text('Save new Key/Value Pair'),
              onPressed: () {
                widget.keyFinder.setKeyValue(
                  _keyTextController.text,
                  _valueTextController.text,
                );
                setState(() {
                  key = _keyTextController.text;
                });
              },
            )
          ],
        ),
      ),
    );
  }
}

quelques captures d'écran

la toile entrez la description de l'image ici entrez la description de l'image ici

mobile entrez la description de l'image ici

Abhilash Chandran
la source
2
Merci pour cet énorme effort! Bien joué. J'étais en attendant sur le même chemin (en regardant dans le package http aussi, ce qui est drôle :)). Merci beaucoup!
Giovanni
1
J'espère que cela aide aussi les autres. Nous apprenons tous en résolvant .. :-)
Abhilash Chandran
Salut essayé votre code a fonctionné! ty. J'ai ensuite découvert le kIsWeb booléen mondial qui peut vous dire si l'application a été compilée pour fonctionner sur le Web. Documentation: api.flutter.dev/flutter/foundation/kIsWeb-constant.html PS - Nouveau pour excuser les excuses à l'avance si j'oublie quelque chose, la mise en œuvre devient beaucoup plus simple si vous l'utilisez
Shamik Chodankar
2
@ShamikChodankar Vous avez raison. Ce drapeau booléen sera utile pour certaines décisions logiques. OP a également essayé cette option. Mais le problème est que si nous utilisons les deux dart:html' and préférences partagées dans la même fonction, le compilateur générera des erreurs car il ne saura pas dart:htmllors de la compilation sur un appareil mobile et au contraire il ne saura pas sharedpreferenceslors de la compilation sur le Web à moins que ses auteurs gérer en interne. Veuillez partager si vous avez un exemple de travail utilisant ce drapeau. Je suis également nouveau pour flotter :).
Abhilash Chandran