Wie importiere ich plattformspezifische Abhängigkeiten in Flutter / Dart? (Web mit Android / iOS kombinieren)

9

Ich verwende shared_preferencesin meiner Flutter-Anwendung für iOS und Android. Im Web verwende ich die http:dartAbhängigkeit ( window.localStorage) selbst. Da Flutter for Web in das Flutter-Repo integriert wurde, möchte ich eine plattformübergreifende Lösung erstellen.

Dies bedeutet, dass ich zwei separate APIs importieren muss. Dies scheint in Dart noch nicht sehr gut unterstützt zu werden, aber das habe ich getan:

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';

In meiner preference_utils_stub.dartDatei habe ich alle Klassen / Variablen implementiert, die während der Kompilierungszeit sichtbar sein müssen:

Window window;

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

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

Dadurch werden alle Fehler vor dem Kompilieren beseitigt. Jetzt habe ich eine Methode implementiert, die überprüft, ob die Anwendung das Web verwendet oder nicht:

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

Dies führt jedoch zu einer Vielzahl von Fehlern:

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];

Und so weiter. Wie kann man ohne diese Fehler je nach Plattform unterschiedliche Methoden / Klassen verwenden? Beachten Sie, dass ich auf diese Weise mehr Abhängigkeiten verwende, nicht nur Einstellungen. Vielen Dank!

Giovanni
quelle
Nach meinem begrenzten Wissen sollten Sie nicht sowohl die localstorageals auch die shared preferencesAbhängigkeiten in derselben Methode oder Klasse haben. Dies bedeutet, dass der Compiler keine dieser Abhängigkeiten baumschütteln kann. Idealerweise sollte der Import diese Implementierungen verbergen. Ich werde versuchen, ein klares Implementierungsbeispiel zu finden.
Abhilash Chandran
Sie können das globale boolesche kIsWeb verwenden, mit dem Sie feststellen können, ob die App für die Ausführung im Web kompiliert wurde oder nicht. Dokumentation: api.flutter.dev/flutter/foundation/kIsWeb-constant.html if (kIsWeb) {// läuft im Web! Web-
Datenbank

Antworten:

20

Hier ist meine Herangehensweise an Ihr Problem. Dies basiert auf den Implementierungen aus dem httpPaket wie hier .

Die Kernidee lautet wie folgt.

  1. Erstellen Sie eine abstrakte Klasse, um die Methoden zu definieren, die Sie verwenden müssen.
  2. Erstellen Sie spezifische Implementierungen webund androidAbhängigkeiten, die diese abstrakte Klasse erweitern.
  3. Erstellen Sie einen Stub, der eine Methode zur Rückgabe der Instanz dieser abstrakten Implementierung verfügbar macht. Dies dient nur dazu, das Dart-Analyse-Tool bei Laune zu halten.
  4. Importieren Sie in der abstrakten Klasse diese Stub-Datei zusammen mit den für mobileund spezifischen bedingten Importen web. Geben Sie dann in seinem Factory-Konstruktor die Instanz der spezifischen Implementierung zurück. Dies wird automatisch durch bedingten Import behandelt, wenn es richtig geschrieben ist.

Schritt 1 und 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();
}

Schritt 2.1: Web Key Finder

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();

Schritt 2.2: Mobile Key Finder

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();

Schritt 3:

import 'key_finder_interface.dart';

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

main.dartVerwenden Sie dann in Ihrer Verwendung die KeyFinderabstrakte Klasse, als wäre sie eine generische Implementierung. Dies ähnelt einem Adaptermuster .

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;
                });
              },
            )
          ],
        ),
      ),
    );
  }
}

Einige Screenshots

Netz Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

Handy, Mobiltelefon Geben Sie hier die Bildbeschreibung ein

Abhilash Chandran
quelle
2
Vielen Dank für diesen großen Aufwand! Gut gemacht. Ich war in der Zwischenzeit auf dem gleichen Weg (siehe auch im http-Paket, was lustig ist :)). Vielen Dank!
Giovanni
1
Hoffe das hilft auch anderen. Wir alle lernen durch Lösen .. :-)
Abhilash Chandran
Hallo, dein Code hat funktioniert! ty. Ich habe dann etwas über das globale boolesche kIsWeb herausgefunden, das Ihnen sagen kann, ob die App für die Ausführung im Web kompiliert wurde oder nicht. Dokumentation: api.flutter.dev/flutter/foundation/kIsWeb-constant.html PS- Neu, um Entschuldigungen im Voraus zu flattern, wenn ich etwas übersehen habe, wird die Implementierung viel einfacher, wenn Sie das verwenden
Shamik Chodankar
2
@ShamikChodankar Du hast recht. Dieses boolesche Flag ist für bestimmte logische Entscheidungen hilfreich. OP hat diese Option ebenfalls ausprobiert. Das Problem ist jedoch, dass dart:html' and der Compiler , wenn wir beide gemeinsam genutzten Einstellungen in derselben Funktion verwenden, Fehler generiert, da er dart:htmlbeim Kompilieren auf einem mobilen Gerät nichts darüber weiß und im Gegensatz dazu nichts über das sharedpreferencesKompilieren gegen das Web weiß , es sei denn, seine Autoren intern damit umgehen. Bitte teilen Sie uns mit, wenn Sie ein funktionierendes Beispiel haben, das dieses Flag verwendet. Ich bin auch neu im Flattern :).
Abhilash Chandran