Come importare la dipendenza specifica della piattaforma in Flutter / Dart? (Combina Web con Android / iOS)


9

Sto usando shared_preferencesnella mia applicazione Flutter per iOS e Android. Sul web sto usando la http:dartdipendenza ( window.localStorage) stessa. Da quando Flutter per il Web è stato unito al repository Flutter, voglio creare una soluzione multipiattaforma.

Ciò significa che devo importare due API separate. Questo non sembra essere ancora ben supportato in Dart, ma è quello che ho fatto:

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

Nel mio preference_utils_stub.dartfile, ho implementato tutte le classi / variabili che devono essere visibili durante il tempo di compilazione:

Window window;

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

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

Questo elimina tutti gli errori prima della compilazione. Ora ho implementato un metodo che controlla se l'applicazione sta usando il web o meno:

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

Tuttavia, questo dà un sacco di errori:

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

E così via. Come si possono usare metodi / classi diversi a seconda della piattaforma senza questi errori? Nota che sto usando più dipendenze in questo modo, non solo preferenze. Grazie!


Nella mia conoscenza limitata non si deve avere sia le localstoragee shared preferencesdipendenze nel stesso metodo o classe. Ciò significa che il compilatore non può eseguire l'handshake di nessuna di queste due dipendenze. Idealmente, l'importazione dovrebbe nascondere queste implementazioni. Proverò a fornire un chiaro esempio di implementazione.
Abhilash Chandran,

È possibile utilizzare il kIsWeb booleano globale che può dirti se l'app è stata compilata per essere eseguita sul Web. Documentazione: api.flutter.dev/flutter/foundation/kIsWeb-constant.html if (kIsWeb) {// in esecuzione sul Web! inizializza web db} else {// usa le preferenze condivise}
Shamik Chodankar l'

Risposte:


20

Ecco il mio approccio al tuo problema. Questo si basa sulle implementazioni dal httppacchetto come qui .

L'idea di base è la seguente.

  1. Crea una classe astratta per definire i metodi che dovrai utilizzare.
  2. Crea implementazioni specifiche webe androiddipendenze che estendono questa classe astratta.
  3. Creare uno stub che espone un metodo per restituire l'istanza di questa implementazione astratta. Questo serve solo a rendere felice lo strumento per l'analisi delle freccette.
  4. Nella classe astratta importare questo file stub insieme alle importazioni condizionali specifiche per mobilee web. Quindi, nel suo costruttore di fabbrica restituisce l'istanza dell'implementazione specifica. Questo verrà gestito automaticamente dall'importazione condizionale se scritto correttamente.

Passaggio 1 e 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();
}

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

Step-2.2: Key finder 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();

Step-3:

import 'key_finder_interface.dart';

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

Quindi nel tuo main.dartuso la KeyFinderclasse astratta come se fosse un'implementazione generica. Questo è un po 'come un modello di adattatore .

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

alcune schermate

ragnatela inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

mobile inserisci qui la descrizione dell'immagine


2
Grazie per questo enorme sforzo! Molto bene. Nel frattempo ero allo stesso modo (anche nel pacchetto http, che è divertente :)). Molte grazie!
Giovanni

1
Spero che questo aiuti anche gli altri. Impariamo tutti risolvendo .. :-)
Abhilash Chandran,

Ciao ho provato a far funzionare il tuo codice! ty. Ho quindi scoperto il kIsWeb booleano globale che può dirti se l'app è stata compilata per essere eseguita sul Web. Documentazione: api.flutter.dev/flutter/foundation/kIsWeb-constant.html PS- Nuovo sconcertare le scuse in anticipo se sto trascurando qualcosa l'implementazione diventa molto più semplice se la usi
Shamik Chodankar

2
@ShamikChodankar Hai ragione. Questa bandiera booleana sarà utile per una certa decisione logica. OP ha provato anche questa opzione. Ma il problema è che se utilizziamo entrambe le dart:html' and preferenze condivise` nella stessa funzione, il compilatore genererà errori perché non è a conoscenzadart:html durante la compilazione su un dispositivo mobile e, al contrario, non lo saprà sharedpreferencesdurante la compilazione contro il web a meno che i suoi autori gestirlo internamente. Si prega di condividere se si dispone di un esempio funzionante che utilizza questo flag. Sono anche nuovo a svolazzare :).
Abhilash Chandran,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.