¿Cómo importar una dependencia específica de la plataforma en Flutter / Dart? (Combinar Web con Android / iOS)

9

Estoy usando shared_preferencesmi aplicación Flutter para iOS y Android. En la web estoy usando la http:dartdependencia ( window.localStorage) en sí. Como Flutter para web se fusionó con el repositorio de Flutter, quiero crear una solución multiplataforma.

Esto significa que necesito importar dos API separadas. Parece que esto todavía no es muy bueno en Dart, pero esto es lo que hice:

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

En mi preference_utils_stub.dartarchivo, implementé todas las clases / variables que deben ser visibles durante el tiempo de compilación:

Window window;

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

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

Esto elimina todos los errores antes de la compilación. Ahora implementé algún método que verifica si la aplicación está usando la web o no:

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

Sin embargo, esto da muchos errores:

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

Y así. ¿Cómo se pueden usar diferentes métodos / clases dependiendo de la plataforma sin estos errores? Tenga en cuenta que estoy usando más dependencias de esta manera, no solo preferencias. ¡Gracias!

Giovanni
fuente
En mi conocimiento limitado, no debe tener ambas dependencias localstoragey shared preferencesen el mismo método o clase. Esto significa que el compilador no puede sacudir los árboles de ninguna de estas dependencias. Idealmente, la importación debería ocultar estas implementaciones. Trataré de llegar a un claro ejemplo de implementación.
Abhilash Chandran el
Puede usar el kIsWeb booleano global que le puede decir si la aplicación se compiló o no para ejecutarse en la web. Documentación: api.flutter.dev/flutter/foundation/kIsWeb-constant.html if (kIsWeb) {// ejecutándose en la web! initialize web db} else {// use preferencias compartidas}
Shamik Chodankar

Respuestas:

20

Aquí está mi enfoque a su problema. Esto se basa en las implementaciones del httppaquete como aquí .

La idea central es la siguiente.

  1. Cree una clase abstracta para definir los métodos que necesitará usar.
  2. Cree implementaciones específicas weby androiddependencias que extiendan esta clase abstracta.
  3. Cree un apéndice que exponga un método para devolver la instancia de esta implementación abstracta. Esto es solo para mantener feliz a la herramienta de análisis de dardos.
  4. En la clase abstracta, importe este archivo apéndice junto con las importaciones condicionales específicas para mobiley web. Luego, en su constructor de fábrica, devuelva la instancia de la implementación específica. Esto se manejará automáticamente mediante importación condicional si se escribe correctamente.

Paso 1 y 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();
}

Paso 2.1: buscador de claves 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();

Paso 2.2: buscador de clave móvil

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

Paso 3:

import 'key_finder_interface.dart';

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

Luego, en su main.dartuso, la KeyFinderclase abstracta como si fuera una implementación genérica. Esto es algo así como un patrón 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;
                });
              },
            )
          ],
        ),
      ),
    );
  }
}

algunas capturas de pantalla

Web ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

móvil ingrese la descripción de la imagen aquí

Abhilash Chandran
fuente
2
¡Gracias por este gran esfuerzo! Bien hecho. Mientras tanto, estaba en la misma forma (buscando también en el paquete http, lo cual es gracioso :)). ¡Muchas gracias!
Giovanni
1
Espero que esto ayude a otros también. Todos aprendemos resolviendo .. :-)
Abhilash Chandran
Hola probé tu código funcionó! ty. Luego descubrí el kIsWeb booleano global que puede decirte si la aplicación se compiló o no para ejecutarse en la web. Documentación: api.flutter.dev/flutter/foundation/kIsWeb-constant.html PD: nuevo para alentar las disculpas de antemano si estoy pasando por alto algo, la implementación se vuelve mucho más simple si la usas
Shamik Chodankar
2
@ShamikChodankar Tienes razón. Esta bandera booleana será útil para ciertas decisiones lógicas. OP también probó esta opción. Pero el problema es que si usamos ambas dart:html' and preferencias compartidas en la misma función, el compilador generará errores porque no sabrá dart:htmlcuando compila contra un dispositivo móvil y, por el contrario, no sabrá sharedpreferencescuando compila contra la web a menos que sus autores manejarlo internamente. Comparta si tiene un ejemplo de trabajo que utiliza esta bandera. También soy nuevo en aleteo :).
Abhilash Chandran