Flutter BLoCProvider新传递的值未更新

0md85ypi  于 2023-04-07  发布在  Flutter
关注(0)|答案(1)|浏览(147)

我是Flutter BLoC的超级新手,我目前正在学习构建股票应用程序的视频教程,同时实现我自己的一些功能。我有下面的代码,每当用户更新新的时,我想更新_apiKey。在下面的代码中,我无法在每次用户更新新的时更新_apiKey。然而,获取输入的第一个API文本并在整个过程中使用它,直到应用程序重新启动,然后它将使用updated _apiKey。

import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../api/api.dart';
import '../components/components.dart';
import '../state/state.dart';
import 'widgets.dart';

class SearchWidget extends StatefulWidget {
  const SearchWidget({Key? key}) : super(key: key);

  @override
  State<SearchWidget> createState() => SearchWidgetState();
}

class SearchWidgetState extends State<SearchWidget> {
  String _apiKey = '';
  String apiKey = '';

  final _loginWidgetNotifier = ValueNotifier<bool>(true);

  bool get _loginWidget => _loginWidgetNotifier.value;

  set _loginWidget(bool value) {
    _loginWidgetNotifier.value = value;
  }

  bool error = false;

  @override
  void initState() {
    super.initState();
    loadApiKey();
    loadLoginState();
  }

  void enterApiKey() {
    showModal(
      configuration: NonDismissibleModalConfiguration(),
      context: context,
      builder: (BuildContext context) {
        return StatefulBuilder(
          builder: (context, setState) {
            return AlertDialog(
              title: const Text('Enter API key'),
              content: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  TextField(
                    obscureText: true,
                    onChanged: (value) {
                      // You can save the API key in a state variable here
                      apiKey = value;
                    },
                    decoration: InputDecoration(
                      hintText: 'API key',
                      errorBorder: UnderlineInputBorder(
                        borderSide: BorderSide(
                          color: error ? Colors.red : Colors.grey,
                        ),
                      ),
                      focusedBorder: UnderlineInputBorder(
                        borderSide: BorderSide(
                          color: error ? Colors.red : Colors.grey,
                        ),
                      ),
                      focusedErrorBorder: UnderlineInputBorder(
                        borderSide: BorderSide(
                          color: error ? Colors.red : Colors.grey,
                        ),
                      ),
                      disabledBorder: UnderlineInputBorder(
                        borderSide: BorderSide(
                          color: error ? Colors.red : Colors.grey,
                        ),
                      ),
                      enabledBorder: UnderlineInputBorder(
                        borderSide: BorderSide(
                          color: error ? Colors.red : Colors.grey,
                        ),
                      ),
                      border: UnderlineInputBorder(
                        borderSide: BorderSide(
                          color: error ? Colors.red : Colors.grey,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
              actions: <Widget>[
                Row(
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: [
                    MaterialButton(
                      child: const Text('Cancel'),
                      onPressed: () {
                        Navigator.of(context).pop();
                      },
                    ),
                    MaterialButton(
                        child: const Text('OK'),
                        onPressed: () {
                          if (apiKey.isNotEmpty) {
                            setState(() {
                              _apiKey = apiKey;
                              _loginWidget = false;
                              error = false;
                              apiKey = '';
                            });
                            saveApiKey();
                            saveLoginState();
                            Navigator.of(context).pop();
                          } else {
                            setState(() {
                              error = true;
                            });
                          }
                        }),
                  ],
                ),
              ],
            );
          },
        );
      },
    );
  }

  void saveApiKey() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setString('FinnhubApiKey', _apiKey);
  }

  void loadApiKey() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      _apiKey = prefs.getString('FinnhubApiKey') ?? '';
    });
  }

  void saveLoginState() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.setBool('LoginState', _loginWidget);
  }

  void loadLoginState() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      _loginWidget = prefs.getBool('LoginState') ?? true;
    });
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => AppStateCubit(Api(Api.buildDefaultHttpClient(_apiKey))),
      child: ValueListenableBuilder(
        valueListenable: _loginWidgetNotifier,
        builder: (context, loginWidgetValue, child) {
          return SingleChildScrollView(
            child: _loginWidget
                ? Container(
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.black),
                      color: Colors.white,
                    ),
                    height: MediaQuery.of(context).size.height / 1.5,
                    width: MediaQuery.of(context).size.width / 4.2,
                    child: Center(
                      child: TextButton.icon(
                        onPressed: () {
                          enterApiKey();
                        },
                        icon: const Icon(
                          Icons.account_circle_rounded,
                          color: Colors.black,
                          size: 42,
                        ),
                        label: const Text(
                          'Stocks',
                          style: TextStyle(
                            color: Colors.black,
                            fontSize: 21,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ),
                  )
                : SearchFormWidget(
                    toggleLoginWidget: () {
                      setState(() {
                        _loginWidget = true; // switch to login widget
                      });
                      saveLoginState();
                    },
                  ),
          );
        },
      ),
    );
  }
}

API类如下

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:goalgamer/models/models.dart';

class Api {
  final Dio _dio;

  Api(this._dio);

  static Dio buildDefaultHttpClient(String apiKey) {
    final dio = Dio();
    dio.options.baseUrl = "https://finnhub.io/api";
    dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) {
        options.queryParameters.addAll({
          "token": apiKey,
        });

        return handler.next(options);
      },
    ));

    return dio;
  }

  Future<List<Candle>> getCandles(GetCandlesRequest request) async {
    try {
      final response = await _dio.get<Map<String, dynamic>>(
        "/v1/stock/candle",
        queryParameters: request.toJson(),
      );

      final candlesPayload = CandlesPayload.fromJson(response.data!);
      return candlesPayload.toCandles();
    } catch (e, s) {
      debugPrint("$e\n$s");
      throw ApiException("An unknown error occurred");
    }
  }
}

class ApiException {
  final String message;

  ApiException(this.message);
}

appstate类如下所示

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:goalgamer/api/api.dart';
import 'package:goalgamer/api/resolution.dart';
import 'package:goalgamer/models/models.dart';
import 'package:goalgamer/state/app_state.dart';

class AppStateCubit extends Cubit<AppState> {
  final Api _api;

  AppStateCubit(this._api) : super(AppState.initial());

  Future<List<Candle>?> loadCandles(
    String symbol,
    DateTime from,
    DateTime to,
  ) async {
    emit(state.copyWith(isLoading: true, hasError: false));

    // Find the first resolution which results in less than 100 candles
    final resolution = Resolution.values.firstWhere(
      (r) {
        final ms = to.millisecondsSinceEpoch - from.millisecondsSinceEpoch;
        final resultingCandles = ms / r.duration.inMilliseconds;
        return resultingCandles < 100;
      },
      orElse: () => Resolution.month,
    );

    try {
      final request = GetCandlesRequest(resolution, to, from, symbol);
      emit(state.copyWith(recentQuery: request));

      final candles = await _api.getCandles(request);

      emit(state.copyWith(
        isLoading: false,
        candles: candles,
        currentSymbol: symbol,
        errorMessage:
            candles.isEmpty ? "No data could be found for your request" : null,
        hasError: candles.isEmpty,
      ));

      return candles;
    } on ApiException catch (e) {
      emit(state.copyWith(
        isLoading: false,
        hasError: true,
        errorMessage: e.message,
      ));
    }
    return null;
  }
}
aurhwmvo

aurhwmvo1#

  • 上面的代码使用了三种状态管理技术:setState、Provider、BLoC。您只需使用其中一个并正确使用它即可。
  • 您可以在此处阅读状态管理选项文档:https://docs.flutter.dev/development/data-and-backend/state-mgmt/options
  • 正如你提到的Flutter BLoC,我写了一个最小的例子,在你的情况下使用BLoC,让你很容易理解它。有一个重要的注意是,你不需要一个StatefulWidget时,使用这个Flutter BLoC。
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// A [Cubit] to manage the API Key.
/// isEmpty: API Key is not set
class APIKeyStateCubit extends Cubit<String> {
  APIKeyStateCubit() : super('');

  void load() {
    /// Read API key from SharedPref and emit it
    // SharedPreferences prefs = await SharedPreferences.getInstance();
    // emit(prefs.getString('FinnhubApiKey') ?? '');
    emit('');
  }

  /// Update the API Key state
  void update(String apiKey) {
    /// Save API key to SharedPref
    // SharedPreferences prefs = await SharedPreferences.getInstance();
    // prefs.setString('FinnhubApiKey', apiKey);

    emit(apiKey);
  }
}

// A [Cubit] to manage the Login state.
/// true: logged-in
class LoginStateCubit extends Cubit<bool> {
  LoginStateCubit() : super(false);

  void load() {
    /// Read LoginState from SharedPref and emit it
    // SharedPreferences prefs = await SharedPreferences.getInstance();
    // emit(prefs.getBool('LoginState') ?? true);
    emit(false);
  }

  /// Update the LoginState state
  void update(bool newState) {
    /// Save LoginState to SharedPref
    // SharedPreferences prefs = await SharedPreferences.getInstance();
    // prefs.setBool('LoginState', newState);

    emit(newState);
  }
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        // Create and Load APIKeyStateCubit Bloc
        BlocProvider(create: (context) => APIKeyStateCubit()..load()),
        // Create and Load LoginStateCubit Bloc
        BlocProvider(create: (context) => LoginStateCubit()..load()),
      ],
      child: const MaterialApp(
        title: 'Cubit',
        home: SearchWidget(),
      ),
    );
  }
}

class SearchWidget extends StatelessWidget {
  final String? title;
  const SearchWidget({super.key, this.title});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: SafeArea(
            child: Column(
      children: [
        // Example using APIKeyStateCubit
        // BlocBuilder will execute whenever apiKey is updated (APIKeyStateCubit is changed)
        BlocBuilder<APIKeyStateCubit, String>(builder: (context, apiKey) {
          return (apiKey.isNotEmpty)
              // show API key if it is not empty
              ? Text('API key: $apiKey')
              // ask user to enter API key if it is empty
              : FutureBuilder<String?>(
                  future: _enterApiKey(context),
                  builder: (
                    BuildContext context,
                    AsyncSnapshot<String?> snapshot,
                  ) {
                    if (snapshot.hasData) {
                      // update Cubit
                      context
                          .read<APIKeyStateCubit>()
                          .update(snapshot.data ?? '');
                    }
                    return const CircularProgressIndicator();
                  });
        }),
        // Example using LoginStateCubit
        // BlocBuilder will execute whenever isLogin is updated (LoginStateCubit is changed)
        BlocBuilder<LoginStateCubit, bool>(builder: (context, isLogin) {
          // show Login state
          return Text('Login state: $isLogin');
        }),
      ],
    )));
  }

  Future<String?> _enterApiKey(BuildContext context) async {
    // force user to enter an API key
    return await Future.delayed(
        const Duration(microseconds: 0),
        () => showDialog<String?>(
              context: context,
              barrierDismissible: false,
              builder: (BuildContext context) {
                var apiKey = '';
                return AlertDialog(
                  title: const Text('Enter API key'),
                  content: TextField(
                    obscureText: true,
                    onChanged: (value) {
                      apiKey = value;
                    },
                  ),
                  actions: <Widget>[
                    MaterialButton(
                        child: const Text('OK'),
                        onPressed: () {
                          if (apiKey.isNotEmpty) {
                            Navigator.of(context).pop(apiKey);
                          }
                        }),
                  ],
                );
              },
            ));
  }
}

希望这能帮上忙。

更新

您必须监听Bloc更改的状态,例如SearchWidget的构建函数可能是:

@override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => AppStateCubit(Api(Api.buildDefaultHttpClient(_apiKey))),
      child: 
      Column(
        children: [
          BlocBuilder<APIKeyStateCubit, AppState>(builder: (context, appState) { return Text('state: $appState');}
          ValueListenableBuilder(
            valueListenable: _loginWidgetNotifier,
            builder: (context, loginWidgetValue, child) {
              return SingleChildScrollView(
                child: _loginWidget
                    ? Container(
                        decoration: BoxDecoration(
                          border: Border.all(color: Colors.black),
                          color: Colors.white,
                        ),
                        height: MediaQuery.of(context).size.height / 1.5,
                        width: MediaQuery.of(context).size.width / 4.2,
                        child: Center(
                          child: TextButton.icon(
                            onPressed: () {
                              enterApiKey();
                            },
                            icon: const Icon(
                              Icons.account_circle_rounded,
                              color: Colors.black,
                              size: 42,
                            ),
                            label: const Text(
                              'Stocks',
                              style: TextStyle(
                                color: Colors.black,
                                fontSize: 21,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ),
                        ),
                      )
                    : SearchFormWidget(
                        toggleLoginWidget: () {
                          setState(() {
                            _loginWidget = true; // switch to login widget
                          });
                          saveLoginState();
                        },
                      ),
              );
            },
          ),
        ],
      ),
    );
  }

相关问题