Flutter如何使用mvi? bloc结合自定义http库的实现

这篇具有很好参考价值的文章主要介绍了Flutter如何使用mvi? bloc结合自定义http库的实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

提示:本篇并不算严谨的科普文章,仅仅只是记录使用bloc的思路
最近对kotlin的mvi使用比较娴熟,但是关于flutter架构相关的比较少,之前也有看过provider这些框架总觉得没那么好使而且还挺麻烦的,现在也有大佬研究getx的mvvm,这里我就不展开了,我的本意是想使用getx作为路由管理框架,将它的状态管理使用bloc替代,别问为什么这样考虑,getx虽然提供了很强大的状态管理,但是总有些缺点,具体的没有去深入研究真假暂不确定,可能企业级使用bloc会多一点,新版本dart提供了一些新功能,怎么说呢依旧感觉没有kotlin好使,本篇文章的目的是为了记录bloc使用的示例,可能会比较依赖multiple_result这个库,但是使用Result返回参数这个概念感觉还不错,dart提供了类似模式匹配的简化版,搭配Result还是挺不错的,本篇文章和Android mvi 三这篇文章思路是一致的,可惜dart对sealed class的支持比较薄弱,可以当作是换了关键字的抽象类,下面就是最终的简单效果,点击按钮请求网络出现加载动画,拿到数据后显示数据,效果图如下:

Flutter如何使用mvi? bloc结合自定义http库的实现


一、先看看如何使用bloc吧

1. 定义页面需要的数据

代码如下:

import '../../lib_base/index.dart';
import '../../models/banner_model.dart';

class MyFromState {
  MyFromState({required this.banner});

/// 可以不写这段代码,将这段代码放在bloc类里面写是可以的
  factory MyFromState.init() {
    return MyFromState(
      banner: InitState()
    );
  }

  final BannerState banner;

  MyFromState copyWith({
    BannerState? banner,
  }) {
    return MyFromState(
      banner: banner ?? this.banner,
    );
  }
}

/// 定义ui状态
sealed class BannerState {}
/// 用于初始化状态实例
class InitState extends BannerState {}
/// 成功返回 - 有数据
class OnSuccess extends BannerState {
  final List<BannerModel> body;
  OnSuccess(this.body);
}
/// 成功返回 - 无数据
class OnNoData extends BannerState {}
/// 加载状态
class OnLoading extends BannerState {
  final LoadingEffect loading;
  OnLoading(this.loading);
}

2. 定义通用加载状态

代码如下:

/// 通用响应事件
sealed class LoadingEffect{}

/// 开启或关闭加载动画, true表示开启加载动画,false表示请求已经结束关闭加载动画
final class Loading extends LoadingEffect {
  final bool isShow;
  Loading(this.isShow);
}

/// 用于处理401需要鉴权的情况
final class OnAuthority extends LoadingEffect {}

3. 定义事件

代码如下:

sealed class MyFromEvent {}

/// 需要发送的事件
class BannerEvent extends MyFromEvent {}

4. 定义bloc

代码如下:

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:multiple_result/multiple_result.dart';

import '../../lib_base/index.dart';
import '../../lib_http/index.dart';
import '../repository/banner_repository.dart';
import 'event.dart';
import 'state.dart';

class MyFromBloc extends Bloc<MyFromEvent, MyFromState> {
  // 负责数据请求
  final BannerRepository _repository = BannerRepository();

  // 这里和mvi的思路一致,初始化state,并绑定对应事件
  MyFromBloc() : super(MyFromState.init()) {
    // 绑定事件,当使用add时就会执行对应事件
    on<BannerEvent>(_bannerEvent);
  }

  /// 对应事件的处理
  void _bannerEvent(BannerEvent event, Emitter<MyFromState> emit) async {
    // http请求
    final response = await _repository.getBanner(
        onLoading: (isShow) =>
            // 发送加载状态
            emit(state.copyWith(banner: OnLoading(Loading(isShow)))));
     // 判断当前请求结果
    switch (response) {
      case Success():
        // 返回加载成功的数据
        emit(state.copyWith(banner: OnSuccess(response.success)));
        break;
      case Error():
        final error = response.error;
        if (error case NoData()) {
          // 返回无数据
          emit(state.copyWith(banner: OnNoData()));
        }
        if (error case RequestFailure()) {
          // 在下面使用中有对这个函数的定义,这个函数对应于具体的业务项目的业务逻辑处理
          requestFailureError(error.code, error.msg,
              () => emit(state.copyWith(banner: OnLoading(OnAuthority()))));
        }
        break;
    }
  }
}

5. 定义UI

代码如下:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../../lib_base/index.dart';
import '../../models/index.dart';
import 'bloc.dart';
import 'event.dart';
import 'state.dart';

class MyFromPage extends StatelessWidget {
  const MyFromPage({super.key});

  
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => MyFromBloc(),
      child: const MyFromView(),
    );
  }
}

class MyFromView extends StatelessWidget {
  const MyFromView({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body:  BlocBuilder<MyFromBloc, MyFromState>(
        builder: body,
      ),
        floatingActionButton: FloatingActionButton(
          onPressed: _requestBanner(context.read<MyFromBloc>()),
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        )
    );
  }

  Widget body(BuildContext context, MyFromState state) {
    // 拿对应状态
    final banner = state.banner;
    // 如果当前状态是OnLoading则进入判断
    if (banner case OnLoading()) {
      final loading = banner.loading;
      switch(loading) {
        case Loading():
          if (loading.isShow) {
            return _buildLoadingView();
          }
          break;
        case OnAuthority():
          // 跳转登录页
          break;
      }
    }
    if (banner case OnSuccess()) {
      // 成功后显示的布局
      return _listView(banner.body);
    }

    // 这里只会在没有数据的时候触发,如果请求在页面初始化后就已经发起了,这个布局是一个无用布局
    return const Center();
  }

  /// 加载中,加载动画可自行替换
  Widget _buildLoadingView() {
    return const SizedBox(
      width: double.maxFinite,
      height: double.maxFinite,
      child: Center(
        child: SizedBox(
          height: 22,
          width: 22,
          child: CircularProgressIndicator(
            strokeWidth: 2,
            // valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryBgBlue),
          ),
        ),
      ),
    );
  }

  Widget _listView(List<BannerModel> dataArray) {
    return ListView.builder(
      itemCount: dataArray.length,
        itemBuilder: (context, index) {
          final data = dataArray[index];
          return Column(
            children: [
              Image.network(data.imagePath),
              Text(data.title, style: const TextStyle(fontSize: 30)),
              Text(data.desc, style: const TextStyle(fontSize: 20)),
            ],
          );
        }
    );
  }

  /// 发起网络请求
  void Function() _requestBanner(MyFromBloc bloc) => () {
    bloc.add(BannerEvent());
  };
}

6. 使用

都已经这么详细了就不贴代码了。

二、lib_http

使用适配器模式的http上层封装

1. request定义

代码如下:

import 'rock_net_adapter.dart';

/// http 请求方式
enum HttpMethod { get, post, put, delete, patch }

/// 请求参数配置
abstract class RockRequest {
  /// 请求路径
  final String url;

  /// 规范子类, 必须要传递的参数
  RockRequest(this.url, [this._method = HttpMethod.get]);

  /// 请求路径
  HttpMethod _method;
  HttpMethod get method => _method;

  //region 请求头参数
  /// 请求头参数
  final Map<String, String> _headers = {};

  /// 提供可访问的 header对象, 注意该对象只能用于访问修改该对象无法影响到实际关联对象的修改
  Map<String, String> get headers => {}..addAll(_headers);

  //endregion

  //region 请求参数
  /// 请求参数
  final Map<String, dynamic> _params = {};

  /// 提供参数的访问
  Map<String, dynamic> get params => {}..addAll(_params);

  //endregion

  //region query参数
  // url参数
  final Map<String, String> _queryParams = {};

  /// 提供参数的访问
  Map<String, String> get queryParams => {}..addAll(_queryParams);

  //endregion

  /// 单独设置适配器
  IRockNetAdapter? _adapter;

  IRockNetAdapter? get adapter => _adapter;

  /// 添加请求头
  RockRequest addHeader(String key, String value);

  /// 添加url query参数
  RockRequest addQuery(String key, dynamic value);

  /// 添加请求参数
  RockRequest addParam(String key, dynamic value);

  /// 指定适配器
  void setAdapter(IRockNetAdapter adapter);

  /// 创建一个新对象
  RockRequest setMethod(HttpMethod method);
}

/// 具体实现
class RockRequestBuilder extends RockRequest {
  RockRequestBuilder(super.url);

  
  RockRequest addHeader(String key, String value) {
    _headers[key] = value;
    return this;
  }

  
  RockRequest addParam(String key, value) {
    _params[key] = value;
    return this;
  }

  
  RockRequest addQuery(String key, value) {
    _queryParams[key] = value;
    return this;
  }

  
  void setAdapter(IRockNetAdapter adapter) {
    _adapter = adapter;
  }

  
  RockRequest setMethod(HttpMethod method) {
    _method = method;
    return this;
  }
}

2. response定义

代码如下:


/// 用于封装请求相关参数与处理
final class RockResponse<T> {
  /// 请求状态码
  final int statusCode;
  /// 返回数据
  final T? data;
  /// 异常或消息
  final String? message;
  /// 任意数据
  dynamic extra;

  RockResponse(this.statusCode, {this.data, this.message, this.extra});

  RockResponse copyWith({
    int? statusCode,
    T? data,
    String? message,
    dynamic extra,
  }) {
    return RockResponse(
      statusCode ?? this.statusCode,
      data: data ?? this.data,
      message: message ?? this.message,
      extra: extra ?? this.extra,
    );
  }
}

3. 适配器接口

代码如下:

import 'package:multiple_result/multiple_result.dart';

import 'rock_adapter_engine.dart';
import 'rock_error.dart';
import 'rock_response.dart';

/// 适配器接口
abstract class IRockNetAdapter {
  Future<Result<RockResponse<T>, RockNetException>> send<T>(RockAdapterEngine config);
}

4. 构建adapter需要的数据

代码如下:

import 'rock_request.dart';

/// 构建adapter需要的数据
final class RockAdapterEngine {
  /// base url
  final String _baseUrl;

  /// 请求数据
  final RockRequest request;

  RockAdapterEngine(this._baseUrl, this.request);

  /// 生成url
  String url() {
    // http 和 https的切换
    final (isHttp, authority) = _authority(_baseUrl);
    final uri = isHttp
        ? Uri.https(authority, request.url,
            request.queryParams.isNotEmpty ? request.queryParams : null)
        : Uri.http(authority, request.url,
            request.queryParams.isNotEmpty ? request.queryParams : null);

    return uri.toString();
  }

  /// 获取域名
  (bool, String) _authority(String url) {
    var urlArray = url.split('//');
    if (urlArray.length <= 1) {
      return (false, '');
    }
    if (url.startsWith('https')) {
      return (true, urlArray[1]);
    } else {
      return (false, urlArray[1]);
    }
  }
}

5. 网络异常统一封装

代码如下:

/// 网络异常统一封装
sealed class RockNetException implements Exception {
  final String _message;
  final int _code;

  RockNetException(this._code, this._message);

  /// 请求状态
  int get code => _code;

  /// 获取异常信息
  String get message => _message;
}

/// 通用错误处理
class RockNetError extends RockNetException {
  RockNetError(super.code, super.message);
}

/// 需要登录异常
class RockNeedLogin extends RockNetError {
  RockNeedLogin() : super(401, '请先登录');
}

/// 无权限访问异常
class RockNeedAuth extends RockNetError {
  RockNeedAuth() : super(403, '无权限访问');
}

/// 404
class NotFoundError extends RockNetError {
  NotFoundError() : super(404, '请求路径不正确');
}

/// 500
class InternalServerError extends RockNetError {
  InternalServerError() : super(500, '服务器内部错误');
}

/// 连接异常
class RockNoNetwork extends RockNetError {
  RockNoNetwork() : super(-1, '当前网络连接异常,请检查网络配置');
}

6. 核心请求类

代码如下:

import 'package:multiple_result/multiple_result.dart';
import '../../lib_utils/log_util.dart';
import 'rock_adapter_engine.dart';
import 'rock_error.dart';
import 'rock_net_adapter.dart';
import 'rock_request.dart';

/// json 类型的数据
typedef JSONData = dynamic;

/// 发起请求
final class RockNet {
  /// 发送请求, dynamic表示为json类型
  Future<Result<JSONData, RockNetException>> send<T>(
      String baseUrl,
      RockRequest request,
      IRockNetAdapter adapter,
      void Function(RockRequest)? block) async {
    // 创建request
    final adapterRequest = RockAdapterEngine(baseUrl, request);
    // 注: 使用时必须要指定adapter
    final newAdapter = request.adapter ?? adapter;
    // 使用拦截器
    block?.call(request);
    // 打印请求参数
    _printRequest(adapterRequest);

    // 开始请求
    final result = await newAdapter.send(adapterRequest);
    switch (result) {
      case Success():
        _log('http send result = ${result.success.data}');
        return Success(result.success.data);
      case Error():
        _log("${result.error}");
        return Error(result.error);
    }
  }

  /// 打印请求数据
  void _printRequest(RockAdapterEngine engine) {
    _log('url = ${engine.url()} ${engine.request.method}');
    _log('headers = ${engine.request.headers}');
    _log('params = ${engine.request.params}');
  }

  /// 打印函数
  void _log(dynamic msg) {
    LogUtil.debug(msg);
  }
}

7. 提供网络访问配置

代码如下:

import 'package:multiple_result/multiple_result.dart';

import '../core/rock_error.dart';
import '../core/rock_net.dart';
import '../core/rock_net_adapter.dart';
import '../core/rock_request.dart';

/// 提供网络访问配置
final class RockNetUtil {
  RockNetUtil._();

  static RockNetUtil get instance => _getInstance();
  static RockNetUtil? _instance;

  static RockNetUtil _getInstance() {
    _instance ??= RockNetUtil._();
    return _instance!;
  }

  /// 网络请求 url
  String? _baseUrl;

  /// 适配器, 一定要指定
  IRockNetAdapter? _adapter;

  /// 拦截器, 用于自定义配置
  void Function(RockRequest request)? _interceptor;

  /// 设置 base url
  RockNetUtil setBaseUrl(String url) {
    _baseUrl = url;
    return this;
  }

  /// 设置适配器
  RockNetUtil setAdapter(IRockNetAdapter adapter) {
    _adapter = adapter;
    return this;
  }

  /// 添加拦截器
  void setInterceptor(void Function(RockRequest request) block) {
    _interceptor = block;
  }

  /// 请求统一封装
  Future<Result<JSONData, RockNetException>> _send<T>(RockRequest request, void Function() callback) async {
    final rockNet = RockNet();
    // 回调设置不同属性
    callback();
    return await rockNet.send(_baseUrl!, request, _adapter!, _interceptor);
  }

  /// 发起get请求
  Future<Result<JSONData, RockNetException>> get<T>(RockRequest request) async {
    return _send<T>(request, () {
      request.setMethod(HttpMethod.get);
    });
  }

  /// post请求
  Future<Result<JSONData, RockNetException>> post<T>(RockRequest request) async {
    return _send<T>(request, () {
      request.setMethod(HttpMethod.post);
    });
  }

  /// put请求
  Future<Result<JSONData, RockNetException>> put<T>(RockRequest request) async {
    return _send<T>(request, () {
      request.setMethod(HttpMethod.put);
    });
  }

  /// delete 请求
  Future<Result<JSONData, RockNetException>> delete<T>(RockRequest request) async {
    return _send<T>(request, () {
      request.setMethod(HttpMethod.delete);
    });
  }

  /// patch 请求
  Future<Result<JSONData, RockNetException>> patch<T>(RockRequest request) async {
    return _send<T>(request, () {
      request.setMethod(HttpMethod.patch);
    });
  }
}

8. dio适配器

代码如下:

import 'dart:io';

import 'package:dio/dio.dart';
import 'package:multiple_result/multiple_result.dart';

import '../../lib_utils/log_util.dart';
import '../core/rock_adapter_engine.dart';
import '../core/rock_error.dart';
import '../core/rock_net_adapter.dart';
import '../core/rock_request.dart';
import '../core/rock_response.dart';

/// dio 适配器
class DioAdapter implements IRockNetAdapter {
  
  Future<Result<RockResponse<T>, RockNetException>> send<T>(
      RockAdapterEngine config) async {
    // 提前调用
    var url = config.url();
    // dio 配置
    var options = Options(
      headers: config.request.headers,
      sendTimeout: const Duration(seconds: 60),
    );

    try {
      final response = await _sendHandle(config, url, options);
      return Success(RockResponse(response?.statusCode ?? -1,
          data: response?.data, message: response?.statusMessage));
    } on DioException catch (e) {
      var response = e.response;
      // 输出当前抛出异常的url
      LogUtil.error('url = ${response?.realUri}');

      // 每一种对应错误都需要写出来
      switch (response?.statusCode) {
        case 401:
          return Error(RockNeedLogin());
        case 403:
          return Error(RockNeedAuth());
        case 404:
          return Error(NotFoundError());
        case 500:
          return Error(InternalServerError());
        default:
          if (e.error is SocketException) {
            return Error(RockNoNetwork());
          }

          // 这里的错误一般不是http请求错误不用特殊处理, ui直接弹出提示即可
          return Error(RockNetError(-1, e.toString()));
      }
    }
  }

  /// 用于处理请求的实际实现
  Future<Response?> _sendHandle(
      RockAdapterEngine config, String url, Options options) async {
    switch (config.request.method) {
      case HttpMethod.get:
        return await Dio().get(url, options: options);
      case HttpMethod.post:
        return await Dio()
            .post(config.url(), data: config.request.params, options: options);
      case HttpMethod.put:
        return await Dio()
            .put(config.url(), data: config.request.params, options: options);
      case HttpMethod.delete:
        return await Dio().delete(config.url(),
            data: config.request.params, options: options);
      case HttpMethod.patch:
        return await Dio()
            .patch(config.url(), data: config.request.params, options: options);
    }
  }
}

9. 抽象数据类型

代码如下:

/// 抽象数据接收类
abstract class BaseResult<T> {
  /// 当前请求是否成功
  bool isSuccess();

  /// 实际要返回的数据
  T? getData();

  /// 可以是业务错误, 也可以是http状态码
  int errCode();

  /// 请求成功但返回失败
  String errMsg();
}

10. HttpBaseRepository

下面中的log打印需要替换成弹窗提示,由于当前只是示例需要各位自行引入替换

import 'package:multiple_result/multiple_result.dart';

import '../../lib_utils/log_util.dart';
import '../core/rock_error.dart';
import '../core/rock_net.dart';
import 'base_result.dart';

/// 请求失败
sealed class ResponseStateError {
  final String? _msg;
  final int _code;

  ResponseStateError(this._code, this._msg);

  /// 请求状态
  int get code => _code;

  /// 获取异常信息
  String? get msg => _msg;
}

/// 没有数据返回
class NoData extends ResponseStateError {
  NoData() : super(0, null);
}

/// 忽略改错误,该错误只用于返回
class DefError extends ResponseStateError {
  DefError() : super(0x0, null);
}

/// 请求失败
class RequestFailure extends ResponseStateError {
  RequestFailure(super.code, super.msg);
}

/// Repository 专属返回类型
typedef RepositoryResult<T> = Result<T, ResponseStateError>;

abstract class HttpBaseRepository {
  /// 帮助请求
  /// onLoading - 当前是否加载
  /// request - 具体请求 RockNetUtil.x
  /// onDataConversion - 将请求数据转换成对应类型
  Future<RepositoryResult<T>> baseRequest<T>({
    /// 当前是否加载
    void Function(bool isShow)? onLoading,

    /// 具体请求
    required Future<Result<JSONData, RockNetException>> Function() request,

    /// 数据转换
    required BaseResult<T?> Function(JSONData response) onDataConversion,
  }) async {
    // 开始加载
    onLoading?.call(true);
    // 开始请求并获取结果
    final result = await request();
    // 关闭加载动画
    onLoading?.call(false);

    // 请求成功
    switch (result) {
      case Success():
        final response = result.success;
        final baseModel = onDataConversion(response);
        if (baseModel.isSuccess()) {
          final data = baseModel.getData();
          return data != null ? Success(data) : Error(NoData());
        } else {
          return Error(RequestFailure(baseModel.errCode(), baseModel.errMsg()));
        }
      case Error():
        final err = result.error;
        switch (err) {
          case RockNeedLogin():
            return Error(RequestFailure(err.code, err.message));
          case RockNeedAuth():
            LogUtil.error('RockNeedAuth = ${err.code}: ${err.message}');
            break;
          case NotFoundError():
            LogUtil.error('NotFoundError = ${err.code}: ${err.message}');
            break;
          case InternalServerError():
            LogUtil.error('InternalServerError = ${err.code}: ${err.message}');
            break;
          case RockNoNetwork():
            LogUtil.error('RockNoNetwork = ${err.code}: ${err.message}');
            break;
          case RockNetError():
            LogUtil.error('RockNetError = ${err.code}: ${err.message}');
            break;
        }
        return Error(DefError());
    }
  }
}

11. 使用片段

代码如下:

/// 请求失败处理, 独立个体方便使用, 上面代码也使用到了这个,这里的封装需要根据对应项目业务来展开
/// 这里只提供一个处理的思路
void requestFailureError(int errCode, String? errMsg, void Function()? onAuth) {
  if (errCode == 401 || errCode == -1001) {
    onAuth?.call();
  }
}
final class TestRepository extends HttpBaseRepository {
  /// 获取轮播图
  Future<RepositoryResult<List<BannerModel>>> getBanner({
    // 请求结束,可用于关闭加载动画
    void Function(bool isShow)? onLoading,
  }) async {
    final request = RockRequestBuilder('/banner/json');
    final response = await baseRequest(
        onLoading: onLoading,
        request: () => RockNetUtil.instance.get(request),
        onDataConversion: (jsonData) => BaseModel.fromJson(
            jsonData, (json) => BannerModel.fromJsonArray(json)));
    return response;
	}
}

void main() {
  // 初始化网络配置
  initHttp();
  runApp(const MyApp());
}
void initHttp() {
  RockNetUtil.instance
  		// 设置base url
      .setBaseUrl('https://www.wanandroid.com')
      // .setBaseUrl("http://192.168.190.128:3000")
      // 设置适配器
      .setAdapter(DioAdapter())
      // 设置拦截器
      .setInterceptor((request) {
    request.addHeader('token', 'test');
  });
}

// 发起请求测试
void _incrementCounter() async {
    // TODO 测试 发起请求
    final response = await repository.getBanner(
        onLoading: (isShow) => LogUtil.error('开启加载状态: $isShow'));
    if (response case Success()) {
      LogUtil.error(response.success);
    }
    if (response case Error()) {
      final error = response.error;
      if (error case NoData()) {
        LogUtil.error('body 为空');
      }
      if (error case RequestFailure()) {
        requestFailureError(error.code, error.msg, () => LogUtil.error('需要登录'));
      }
    }
}

总结

// 使用到的库
# 状态管理
flutter_bloc: ^8.1.3
# 网络框架
dio: ^5.1.2
# json序列化注解
json_annotation: ^4.8.0
# 日志
logger: ^1.3.0
# result的一种实现
multiple_result: ^5.0.0

以上就是本篇的全部代码,如果感觉思路不太清晰可以先去了解mvi架构图来对照看,bloc的demo示例比较少,官方demo感觉不太能理解其意,所以将mvi思路照搬一下就解释得通了,http库中使用到了multiple_result作为数据返回核心,这样不需要使用try catch来对异常捕获,很好的解决了数据返回异常的特点,如有其他思路欢迎交流讨论。文章来源地址https://www.toymoban.com/news/detail-506126.html

到了这里,关于Flutter如何使用mvi? bloc结合自定义http库的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Flutter之hydrated_bloc源码分析

    Flutter_Bloc是状态管理组件,hydrated_bloc是 Flutter_Bloc的扩展,它可以在APP重启的情况下,自动记录上次APP的状态。android中可以使用SharePreference来实现状态记录,在Flutter之hydrate_bloc组件入门指南一文中已经讲解了其基本用法,本篇博文就不对其原理进行简单分析,以计数器demo为

    2024年02月09日
    浏览(37)
  • Flutter 之Bloc入门指南实现倒计时功能

    使用Bloc开发Flutter的项目,其项目结构都很简单明确,需要创建状态,创建事件,创建bloc,创建对应的View。flutter_timer项目来分析下Bloc的使用方法。 通过这篇博客,你

    2024年02月14日
    浏览(52)
  • ThinkPHP 验证码扩展库的使用,以及多应用模式下,如何自定义验证码校验规则

    首先,验证码扩展库是需要view扩展的 安装完成后,接着安装验证码扩展库 视图使用的说明: 页面使用的话,两种方式 侧重说明一下,使用第二种方式 我们只需要在控制器中提供一个方法,用于验证码的生成,然后前端将img的src属性修改为对应的方法路径即可 属性说明:

    2024年02月10日
    浏览(41)
  • 【Linux】动静态库的使用与软链接的结合

    库本质就是把一堆(.o)后缀的文件也就是目标文件整合在一起 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库 在makefile中 1.第三方库的使用,gcc往后必须加上 -l +库名 2.如果系统中只提供静态链接,则gcc对其进行静态链

    2024年02月05日
    浏览(36)
  • Java 11 HTTP Client库的使用

    每种编程语言里最常用的库恐怕是Http请求库了,如python里的requests包,nodejs里的request模块。 在Java世界里,也是百花齐放,山头林立。常用的有: HttpURLConnection: 最早的JDK提供的类 Java 11提供的HttpClient Apache HttpComponents项目中的HTTPClient Square提供的OkHttpClient Spring 自带的WebClien

    2024年02月09日
    浏览(31)
  • 组件库的使用和自定义组件

    目录 一、组件库介绍 1、什么是组件 2、组件库介绍 3、arco.design 二、组件库的使用 1、快速上手 2、主题定制 3、暗黑模式 4、语言国际化 5、业务常见问题 三、自定义组件 2、组件开发规范 3、示例实践guide-tip 4、业务组件快速托管 (1)工业:具有标准接口和某种功能且可复

    2024年02月11日
    浏览(49)
  • flutter系列之:如何自定义动画路由

    目录 简介 自定义跳转使用 flutter动画基础 实现一个自定义的route 总结 flutter中有默认的Route组件,叫做MaterialPageRoute,一般情况下我们在flutter中进行跳转的话,只需要向Navigator中传入一个MaterialPageRoute就可以了。 但是MaterialPageRoute太普通了,如果我们想要做点不同的跳转特效

    2023年04月19日
    浏览(46)
  • Flutter Scrollbar滑动条与SingleChildScrollView的结合使用的小细节

    我在业务开发中,ListView是竖向滑动的,然后 ListView中的每一个小条目比较长,我需要横向滑动,所以 就有了 ListView中多个SingleChildScrollView(横向滑动),但是在视觉上,我期望告知用户可以横向滑动,所以有了 Scrollbar 结合 SingleChildScrollView 来使用。 但是两者来使用,多多少少

    2024年01月18日
    浏览(38)
  • 【Python beautifulsoup】详细介绍beautifulsoup库的使用方法,包括安装方式、基本用法、常用方法和技巧,以及结合lxml和parsel的具体使用场景和区别。

    Python beautifulsoup库是一个强大的Web抓取和解析库,它提供了丰富的功能和简单易用的API,可以帮助我们处理HTML和XML文档,从中提取数据,进行数据清洗和处理。beautifulsoup库基于Python标准库中的html.parser模块,同时还可以与第三方解析库lxml和parsel配合使用,提供更高效和灵活的

    2024年02月04日
    浏览(60)
  • Nuxt3最终篇【自定义插件与UI组件库的使用】

    首先我们要知道插件是在plugins文件夹下定义的,让我们开始编写吧 需要在根目录下创建 plugins 文件夹,nuxt会自动识别引用 这里我创建了plugins文件夹—》testplug.ts 使用该定义的pengke 这样就能使用定义好的 pengke 了 每个框架都需要用到第三方组件库,这里我演示使用 element-ui-p

    2024年02月11日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包