Como importar dependência específica da plataforma no Flutter / Dart? (Combine Web com Android / iOS)


9

Estou usando shared_preferencesno meu aplicativo Flutter para iOS e Android. Na web, estou usando a própria http:dartdependência ( window.localStorage). Como o Flutter for web foi mesclado ao repositório do Flutter, desejo criar uma solução de plataforma cruzada.

Isso significa que eu preciso importar duas APIs separadas. Ainda não parece ser muito bom para o Dart, mas foi o que fiz:

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

No meu preference_utils_stub.dartarquivo, implementei todas as classes / variáveis ​​que precisam estar visíveis durante o tempo de compilação:

Window window;

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

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

Isso elimina todos os erros antes da compilação. Agora eu implementei algum método que verifica se o aplicativo está usando a web ou não:

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

No entanto, isso fornece muitos erros:

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 assim por diante. Como se pode usar métodos / classes diferentes, dependendo da plataforma, sem esses erros? Observe que estou usando mais dependências dessa maneira, não apenas preferências. Obrigado!


No meu conhecimento limitado, você não deve ter as dependências localstoragee shared preferencesno mesmo método ou classe. Isso significa que o compilador não pode agitar em árvore as duas dependências. Idealmente, a importação deve ocultar essas implementações. Vou tentar criar um exemplo claro de implementação.
Abhilash Chandran

Você pode usar o kIsWeb booleano global, que pode informar se o aplicativo foi compilado ou não para execução na web. Documentação: api.flutter.dev/flutter/foundation/kIsWeb-constant.html if (kIsWeb) {// em execução na Web! db web initialize} else {// uso compartilhado preferências}
Shamik Chodankar

Respostas:


20

Aqui está a minha abordagem para o seu problema. Isso é baseado nas implementações do httppacote, como aqui .

A idéia central é a seguinte.

  1. Crie uma classe abstrata para definir os métodos que você precisará usar.
  2. Crie implementações específicas webe androiddependências que estendem essa classe abstrata.
  3. Crie um stub que exponha um método para retornar a instância desta implementação abstrata. Isso é apenas para manter feliz a ferramenta de análise de dardo.
  4. Na classe abstrata, importe esse arquivo stub junto com as importações condicionais específicas para mobilee web. Em seguida, em seu construtor de fábrica, retorne a instância da implementação específica. Isso será tratado automaticamente por importação condicional, se escrito corretamente.

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

Etapa 2.1: Localizador de chave da 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();

Etapa 2.2: Localizador de chave móvel

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

Etapa 3:

import 'key_finder_interface.dart';

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

Então, no seu main.dartuso, a KeyFinderclasse abstract como se fosse uma implementação genérica. É um pouco como um padrão de adaptador .

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

algumas capturas de tela

Rede insira a descrição da imagem aqui insira a descrição da imagem aqui

Móvel insira a descrição da imagem aqui


2
Obrigado por este enorme esforço! Bem feito. Enquanto isso, eu estava da mesma maneira (olhando também no pacote http, o que é engraçado :)). Muito obrigado!
Giovanni

11
Espero que isso ajude outras pessoas também. Todos nós aprendemos, resolvendo .. :-)
Abhilash Chandran

Oi tentei o seu código funcionou! ty. Descobri o kIsWeb booleano global, que pode dizer se o aplicativo foi ou não compilado para rodar na web. Documentação: api.flutter.dev/flutter/foundation/kIsWeb-constant.html PS- Novo para desculpas vibração de antecedência, se eu estou com vista para a implementação algo se torna muito mais simples se você usar isso
Shamik Chodankar

2
@ShamikChodankar Você está certo. Esse sinalizador booleano será útil para determinadas decisões lógicas. O OP também tentou essa opção. Mas o problema é que, se usarmos as duas dart:html' and preferências compartilhadas na mesma função, o compilador gerará erros porque não saberá dart:htmlquando estiver compilando em um dispositivo móvel e, pelo contrário, não saberá sharedpreferencesquando estiver compilando na web, a menos que seus autores lidar com isso internamente. Compartilhe se você tiver um exemplo de trabalho utilizando esse sinalizador. Eu também sou novo em agitar :).
Abhilash Chandran
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.