Невозможно вызвать Flutter Singleton из другого пакета

Я пытаюсь импортировать асинхронную функцию во Flutter для безопасного хранения пользовательских данных. Проблема в том, что я получаю следующую ошибку:

packages/authentication_repository/lib/src/authentication_repository.dart:64:15:
Error: Method not found: 'set'. await SecureStorageService.set(
                                      ^^^

Вот мой код:

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureStorageService {
  static SecureStorageService _intance;
  FlutterSecureStorage flutterSecureStorage;

  SecureStorageService._internal() {
    this.flutterSecureStorage = new FlutterSecureStorage();
  }

  static Future<SecureStorageService> getInstance() async {
    if (_intance == null) {
      _intance = SecureStorageService._internal();
    }
    return _intance;
  }

  Future<void> set(String key, String value) async {
    await this.flutterSecureStorage.write(key: key, value: value);
  }

  Future<String> get(String key) async {
    return await this.flutterSecureStorage.read(key: key);
  }

  Future<void> clear() async {
    await this.flutterSecureStorage.deleteAll();
  }
}

Затем я импортирую код, как показано ниже:

import 'package:crowdplan_flutter/storage_util.dart';

...

class AuthenticationRepository {
  final _controller = StreamController<AuthenticationStatus>();
  final secureStorage = SecureStorageService.getInstance();

...

    try {
      final response = await http.post(
        url,
        headers: <String, String>{
          'Content-Type': 'application/json; charset=UTF-8',
        },
        body: jsonEncode(<String, String>{
          'email': email,
          'password': password,
          'client_id': clientId,
        }),
      );
      if (response.statusCode == 200) {
        print(response.body);
        print(json.decode(response.body)['access_token']);
        print(json.decode(response.body)['refresh_token']);
        await secureStorage.set(
            key: 'access_token',
            value: json.decode(response.body)['access_token']);
        await secureStorage.set(
            key: 'refresh_token',
            value: json.decode(response.body)['refresh_token']);
        await secureStorage.set(
            key: 'user_id', value: json.decode(response.body)['user_id']);
        _controller.add(AuthenticationStatus.authenticated);
      }
    } catch (error, stacktrace) {
      print('Exception occurred: $error stackTrace: $stacktrace');
    }
  }

Мой синглтон запускается в моем файле main.dart вот так.

void main() async {
  await SecureStorageService.getInstance();
  runApp(App(
    authenticationRepository: AuthenticationRepository(),
    userRepository: UserRepository(),
  ));
}

Я новичок во Flutter, так что это может быть новая ошибка новичка.


person cp-stack    schedule 16.03.2021    source источник


Ответы (1)


set метод не static, и к нему нельзя получить доступ с помощью SecureStorageService.set

  Future<void> set(String key, String value) async {
    await this.flutterSecureStorage.write(key: key, value: value);
  }

Во втором фрагменте кода я вижу, что вы присвоили синглтон secureStorage.

Вы хотели получить к нему доступ с помощью чего-то вроде ?:

  secureStorage.set()

Часть 2 - Пример кода

Возможно, async getInstance() в классе singleton сбивает вас с толку. Это не обязательно должно быть асинхронным (да и не должно быть). (В некоторых случаях вам может понадобиться асинхронный инициализатор вместо конструктора. См. внизу примера кода здесь для использования -кейс.)

SecureStorageService (синглтон) создается в вашем main() методе, поэтому внутри AuthenticationRepository он будет использовать тот же экземпляр и будет готов к использованию.

class AuthenticationRepository {
  final secureStorage = SecureStorageService.getInstance;
  // ↑ will get the same instance created in main()

Образец кода в вопросе не указывает, где / когда вызывается метод http.post, но я предполагаю, что это инициализация / настройка для AuthenticationRepository, поэтому я смоделировал метод initStorage() внутри него.

Этот вызов initStorage() будет использовать SecureStorageService синглтон с вызовом его secureStorage.set() метода.

Надеюсь, этот пример поможет вам определить разницу между нашими примерами кода, чтобы понять, что происходит не так.

import 'package:flutter/material.dart';

/// Mocking FlutterSecureStorage
/// Note that the actual package FlutterSecureStorage does not have an async
/// constructor nor initializer
class FlutterSecureStorage {
  Map<String,String> data = {};

  Future<void> write({String key, String value}) async {
    data[key] = value;
  }

  Future<String> read({String key}) async {
    print('FSS read - returning value: ${data[key]}');
    return data[key];
  }
}

class SecureStorageService {
  /// for singleton ↓ instance should be final and uses private constructor
  static final SecureStorageService _instance = SecureStorageService._internal();
  FlutterSecureStorage flutterSecureStorage;

  /// Private constructor, not async
  SecureStorageService._internal() {
    flutterSecureStorage = FlutterSecureStorage();
  }

  /// This doesn't need to be async. FlutterSecureStorage (FSS) doesn't have an async initializer
  /// and constructors are generally never async
  /*static Future<SecureStorageService> getInstance() async {
    if (_instance == null) {
      _instance = SecureStorageService._internal();
    }
    return _instance;
  }*/

  /// static singleton instance getter, not async
  static SecureStorageService get getInstance => _instance;

  /// don't need "this" keyword & could use FSS methods directly, but leaving as is
  Future<void> set({String key, String value}) async {
    await flutterSecureStorage.write(key: key, value: value);
  }

  Future<String> get({String key}) async {
    return flutterSecureStorage.read(key: key);
  }
}

class Response {
  int statusCode = 200;

  Response() {
    print('http post completed');
  }
}

class AuthenticationRepository {
  final secureStorage = SecureStorageService.getInstance;
  String accessToken = '';

  /// Encapsulates the slow init of a http.post call. When all ready, returns
  /// the AuthenticationRepository in a usable state
  Future<AuthenticationRepository> initStorage() async {
    try {
      // Mock http response
      final response = await Future.delayed(Duration(seconds: 2), () => Response());

      if (response.statusCode == 200) {
        accessToken = 'access_token from http value';
        await secureStorage.set(
            key: 'access_token',
            value: accessToken);
        print('access token set');
        // snipped other calls for brevity
      }
    } catch (error, stacktrace) {
      print('Exception occurred: $error stackTrace: $stacktrace');
    }
    return this;
  }
}

class SingleStoragePage extends StatefulWidget {

  @override
  _SingleStoragePageState createState() => _SingleStoragePageState();
}

class _SingleStoragePageState extends State<SingleStoragePage> {
  Future<AuthenticationRepository> authRepo;

  @override
  void initState() {
    super.initState();
    authRepo = AuthenticationRepository().initStorage();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Singleton Storage'),
      ),
      body: Center(
        child: FutureBuilder<AuthenticationRepository>(
          future: authRepo,
          builder: (context, snapshot) {
            print('FutureBuilder re/building');
            if (snapshot.hasData) {
              return Text('Access token: ${snapshot.data.accessToken}');
            }
            return Text('loading...');
          },
        ),
      ),
    );
  }
}
person Baker    schedule 16.03.2021
comment
да. Но я уже создаю экземпляр Singleton в своем основном файле. Я не должен указывать это в этом пакете правильно? - person cp-stack; 16.03.2021
comment
Это не то, как работает синглтон. Синглтон просто гарантирует, что у вас есть только один экземпляр. Он не делает нестатические методы статическими (т.е. доступными без экземпляра, запускаемыми из самого определения класса). Поэтому, если вы попытаетесь создать экземпляр SecureStorageService, когда экземпляр уже существует, вы вернете уже созданный экземпляр. - person Baker; 16.03.2021
comment
Я понимаю, что, однако, я не могу создать экземпляр Singleton. Жалуется, что метод не найден. Я предполагаю, что мой настоящий вопрос в том, как я могу правильно создать экземпляр Singleton в этом файле? - person cp-stack; 16.03.2021
comment
Я добавил образец кода, который, надеюсь, иллюстрирует типичную настройку синглтона и его создание для Dart. - person Baker; 17.03.2021
comment
Большое спасибо. Ты был совершенно прав насчет того, что меня сбило с толку. На основе примеров, которые я видел в Интернете, я предположил, что у SFF есть метод экземпляра, который можно вызвать. - person cp-stack; 17.03.2021