无法从另一个包调用 Flutter Singleton

Cannot call Flutter Singleton from another package

我正在尝试在 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 的新手,所以这可能是一个新的菜鸟错误。

set 方法不是 static 并且无法使用 SecureStorageService.set

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

我在第二个代码片段中看到您已将单例分配给 secureStorage

您是想通过类似的方式访问它吗?:

  secureStorage.set()

第 2 部分 - 代码示例

也许单例 class 中的 async getInstance() 给你添麻烦了。它不需要是异步的(也不应该是)。 (在某些情况下,您可能需要异步初始化程序而不是构造函数。有关用例,请参阅 。)

SecureStorageService(单例)在您的 main() 方法中被实例化,因此在 AuthenticationRepository 内部它将使用相同的实例并准备好使用。

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

问题中的代码示例未指定 where/when 正在调用 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...');
          },
        ),
      ),
    );
  }
}