Как импортировать зависимости от платформы во Flutter / Dart? (Объединить веб с Android / iOS)

9

Я использую shared_preferencesв своем приложении Flutter для iOS и Android. В Интернете я использую саму http:dartзависимость ( window.localStorage). Поскольку Flutter для веб-сайтов был объединен с репозиторием Flutter, я хочу создать кроссплатформенное решение.

Это означает, что мне нужно импортировать два отдельных API. Кажется, это еще не очень хорошо поддерживается в Dart, но это то, что я сделал:

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

В моем preference_utils_stub.dartфайле я реализовал все классы / переменные, которые должны быть видны во время компиляции:

Window window;

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

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

Это избавляет от всех ошибок перед компиляцией. Теперь я реализовал некоторый метод, который проверяет, использует ли приложение Интернет или нет:

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

Тем не менее, это дает массу ошибок:

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

И так далее. Как можно использовать разные методы / классы в зависимости от платформы без этих ошибок? Обратите внимание, что таким образом я использую больше зависимостей, а не просто настройки. Спасибо!

Giovanni
источник
Насколько я знаю, вы не должны иметь и то localstorageи другое shared preferencesв одном методе или классе. Это означает, что компилятор не может согласовать любую из этих зависимостей. В идеале импорт должен скрывать эти реализации. Я постараюсь придумать четкий пример реализации.
Абхилаш Чандран
Вы можете использовать глобальный логический kIsWeb, который может сказать вам, было ли приложение скомпилировано для запуска в Интернете. Документация: api.flutter.dev/flutter/foundation/kIsWeb-constant.html if (kIsWeb) {// работающий в Интернете! инициализировать веб-базу данных} else {// использовать общие настройки}
Шамик Чоданкар

Ответы:

20

Вот мой подход к вашей проблеме. Это основано на реализациях из httpпакета, как здесь .

Основная идея заключается в следующем.

  1. Создайте абстрактный класс для определения методов, которые вам нужно будет использовать.
  2. Создание реализаций, специфичных для webи androidзависимостей, расширяющих этот абстрактный класс.
  3. Создайте заглушку, которая предоставляет метод для возврата экземпляра этой абстрактной реализации. Это только для того, чтобы инструмент анализа дротиков был доволен.
  4. В абстрактном классе импортируйте этот файл-заглушку вместе с условным импортом, специфичным для mobileи web. Затем в конструктор фабрики возвращают экземпляр конкретной реализации. Это будет автоматически обработано условным импортом, если написано правильно.

Шаг 1 и 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();
}

Шаг 2.1: Поиск ключей в Интернете

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

Шаг-2.2: Мобильный ключ поиска

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

Шаг 3:

import 'key_finder_interface.dart';

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

Затем вы main.dartиспользуете KeyFinderабстрактный класс, как если бы это была универсальная реализация. Это похоже на шаблон адаптера .

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

несколько скриншотов

Web введите описание изображения здесь введите описание изображения здесь

мобильный введите описание изображения здесь

Абхилаш Чандран
источник
2
Спасибо за это огромное усилие! Отлично сработано. В то же время я был на том же пути (глядя на пакет http, что забавно :)). Большое спасибо!
Джованни
1
Надеюсь, что это помогает другим. Мы все учимся, решая .. :-)
Абхилаш Чандран
Привет попробовал ваш код работал! ти. Затем я узнал о глобальном логическом kIsWeb, который может сказать вам, было ли приложение скомпилировано для запуска в Интернете. Документация: api.flutter.dev/flutter/foundation/kIsWeb-constant.html PS - новость, чтобы заранее извиниться, если я пропускаю что-то, реализация становится намного проще, если вы используете это
Shamik Chodankar
2
@ShamikChodankar Вы правы. Этот логический флаг будет полезен для определенного логического решения. ОП тоже попробовал этот вариант. Но проблема в том, что если мы будем использовать оба dart:html' and sharedpreferences` в одной и той же функции, компилятор выдаст ошибки, потому что он не будет знать о dart:htmlкомпиляции с мобильным устройством и, наоборот, не будет знать об этом sharedpreferencesпри компиляции против веба, если только его авторы справиться с этим внутренне. Пожалуйста, поделитесь, если у вас есть рабочий пример, использующий этот флаг. Я также новичок, чтобы трепетать :).
Абхилаш Чандран