Jak zaimportować zależność od platformy w Flutter / Dart? (Połącz sieć z Androidem / iOS)

9

Używam shared_preferencesw mojej aplikacji Flutter na iOS i Androida. W sieci używam samej http:dartzależności ( window.localStorage). Ponieważ Flutter dla sieci został włączony do repozytorium Flutter, chcę stworzyć rozwiązanie dla wielu platform.

Oznacza to, że muszę zaimportować dwa osobne interfejsy API. Wydaje się, że nie jest to jeszcze zbyt dobrze obsługiwane w Dart, ale to właśnie zrobiłem:

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

W moim preference_utils_stub.dartpliku zaimplementowałem wszystkie klasy / zmienne, które muszą być widoczne podczas kompilacji:

Window window;

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

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

Pozbywa się to wszystkich błędów przed kompilacją. Teraz zaimplementowałem jakąś metodę, która sprawdza, czy aplikacja korzysta z sieci, czy nie:

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

Daje to jednak mnóstwo błędów:

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

I tak dalej. Jak można korzystać z różnych metod / klas w zależności od platformy bez tych błędów? Zauważ, że w ten sposób korzystam z większej liczby zależności, a nie tylko preferencji. Dzięki!

Giovanni
źródło
Według mojej ograniczonej wiedzy nie powinieneś mieć zarówno zależności, jak localstoragei shared preferenceszależności w tej samej metodzie lub klasie. Oznacza to, że kompilator nie może wstrząsnąć żadną z tych zależności. Idealnie import powinien ukryć te implementacje. Spróbuję wymyślić jasny przykład implementacji.
Abhilash Chandran
Możesz użyć globalnego logicznego kIsWeb, który powie ci, czy aplikacja została skompilowana do działania w Internecie. Dokumentacja: api.flutter.dev/flutter/foundation/kIsWeb-constant.html if (kIsWeb) {// działa w sieci! zainicjuj web db} else {// użyj wspólnych preferencji}
Shamik Chodankar,

Odpowiedzi:

20

Oto moje podejście do twojego problemu. Jest to oparte na implementacjach z httppakietu, jak tutaj .

Podstawowa idea jest następująca.

  1. Utwórz klasę abstrakcyjną, aby zdefiniować metody, których będziesz potrzebować.
  2. Twórz implementacje specyficzne webi androidzależne, które rozszerzają tę klasę abstrakcyjną.
  3. Utwórz kod pośredniczący, który udostępnia metodę zwracającą instancję tej abstrakcyjnej implementacji. Ma to na celu zadowolenie narzędzia analizy rzutek.
  4. W klasie abstrakcyjnej zaimportuj ten plik pośredniczący wraz z importem warunkowym specyficznym dla mobilei web. Następnie w konstruktorze fabrycznym zwróć instancję konkretnej implementacji. Zostanie to obsłużone automatycznie przez import warunkowy, jeśli zostanie poprawnie napisany.

Krok 1 i 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();
}

Krok 2.1: Wyszukiwarka kluczy internetowych

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

Krok 2.2: Wyszukiwarka kluczy mobilnych

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

Krok 3:

import 'key_finder_interface.dart';

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

Następnie main.dartwykorzystaj KeyFinderklasę abstrakcyjną, jakby to była ogólna implementacja. To jest trochę jak wzorzec adaptera .

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

kilka zrzutów ekranu

Sieć wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

mobilny wprowadź opis zdjęcia tutaj

Abhilash Chandran
źródło
2
Dzięki za ten ogromny wysiłek! Dobra robota. W międzyczasie byłem w ten sam sposób (również w pakiecie http, co jest zabawne :)). Wielkie dzięki!
Giovanni
1
Mam nadzieję, że to pomaga również innym. Wszyscy uczymy się, rozwiązując .. :-)
Abhilash Chandran
Cześć, próbowałem Twój kod działał! ty. Następnie dowiedziałem się o globalnym kodzie logicznym kIsWeb, który może powiedzieć, czy aplikacja została skompilowana do działania w Internecie. Dokumentacja: api.flutter.dev/flutter/foundation/kIsWeb-constant.html PS- Nowość w trzepotaniu z góry, jeśli przeoczę jakieś wdrożenie, staje się o wiele prostsze, jeśli go użyjesz
Shamik Chodankar
2
@ShamikChodankar Masz rację. Ta flaga logiczna będzie pomocna w przypadku niektórych logicznych decyzji. OP również wypróbował tę opcję. Problem polega jednak na tym, że jeśli użyjemy obu dart:html' and wspólnych preferencji w tej samej funkcji, kompilator wygeneruje błędy, ponieważ nie będzie wiedział o dart:htmlkompilacji na urządzeniu mobilnym, a wręcz przeciwnie, nie będzie wiedział o sharedpreferenceskompilacji w sieci, chyba że jego autorzy radzić sobie z tym wewnętrznie. Udostępnij, jeśli masz działający przykład wykorzystujący tę flagę. Jestem również nowy, aby trzepotać :).
Abhilash Chandran,