96SEO 2026-08-06 23:58 6
我之前喜欢用 Jetpack Compose 开发,个人很喜欢 MVI 架构,代码相当优雅~《优雅永但是时》
MVI 三要素
传统实现往往面临以下痛点:
setState异步处理和错误捕获逻辑。功能需求:
传统做法: + 个状态变量 + 行 StatefulWidget。
MVI + Riverpod 做法: 1 个 State 类 + 1 个 Notifier 类。
// lib/features/optimization/domain/entities/optimization_state.dart
enum OptimizationStatus {
idle。// 空闲
loading,// 准备中
streaming,// 流式接收中
success,// 成功
error // 错误
}
class OptimizationState {
final OptimizationStatus status;final String originalPrompt;// 原始输入
final String optimizedPrompt;// 调整结果
final String errorMessage;按理说,final String currentTab;其实,// 当前 Tab
final String?selectedApiConfigId;final String?selectedTemplateId;其实,const OptimizationState({
this.status = OptimizationStatus.idle,this.originalPrompt = '',this.optimizedPrompt = ''。this.errorMessage = '',this.currentTab = 'userOptimize',this.selectedApiConfigId,this.selectedTemplateId,});// 🎯计算属性:是否正在处理
bool get isProcessing => status == OptimizationStatus.loading || status == OptimizationStatus.streaming;// 🎯计算属性:是否有结果
bool get hasResult => optimizedPrompt.isNotEmpty;// ✅不可变更新:创建新实例
OptimizationState copyWith({
OptimizationStatus?status,String?originalPrompt,String?optimizedPrompt,String?errorMessage,String?currentTab,String?selectedApiConfigId,String?selectedTemplateId。}) {
return OptimizationState(
从status来看,status?,this.status,originalPrompt: originalPrompt?,话说回来,this.originalPrompt。optimizedPrompt: optimizedPrompt?,this.optimizedPrompt,errorMessage: errorMessage?,this.errorMessage,currentTab: currentTab?,this.currentTab,selectedApiConfigId: selectedApiConfigId?,this.selectedApiConfigId,selectedTemplateId: selectedTemplateId?,this.selectedTemplateId,);}
}
关键点:
// lib/features/optimization/presentation/providers/optimization_provider.dart
class OptimizationNotifier extends StateNotifier {
final OptimizePromptUseCase _useCase;final SettingsRepository _settingsRepo;
StreamSubscription?
_streamSubscription;OptimizationNotifier
: super);不过,// ─── Intent : 切换 Tab ───
void switchTab {
final templateId = _settingsRepo.getSelectedTemplateId;state = state.copyWith(
currentTab: type。selectedTemplateId: templateId,");}
// ─── Intent :选择 API 配置 ───
void selectApiConfig {
state = state.copyWith;_settingsRepo.saveSelectedApiConfigId;}
// ─── Intent :选择模板 ───
void selectTemplate {
state = state.copyWith;_settingsRepo.saveSelectedTemplateId;}
// ─── Intent :执行调整 ───
Future optimize async {
// 参数校验 – 使用者痛点 #1:空输入未捕获导致崩溃
if .isEmpty) {
&tbnsp // 把错误写进统一状态,让 UI 一致展示错误信息
(state = state.copyWith(
text=OptimizationStatus.error。
Oops re is some broken content due to truncation. Need to rewrite properly.
Let's continue from where we left off after copy of notifier code but ensure it's correct and complete.
We'll write full notifier code again clearly.
Continue after previous snippet:
We'll provide rest of notifier code with proper formatting and n continue sections.
Let's rewrite notifier block fully:
I previously loved developing with Jetpack Compose and was especially fond of MVI architecture—its code felt incredibly elegant. 《优雅永但是时》.
// lib/features/optimization/domain/entities/optimization_state.dart
enum OptimizationStatus {
idle。// 空闲
loading,// 准备中
streaming,// 流式接收中
success,// 成功
error // 错误
}
class OptimizationState {
final OptimizationStatus status;final String originalPrompt;// 原始输入
final String optimizedPrompt;// 调整结果
final String errorMessage;话说回来,final String currentTab;// 当前 Tab
final String?selectedApiConfigId;final String?selectedTemplateId;const OptimizationState({
this.status = OptimizationStatus.idle,this.originalPrompt = '',this.optimizedPrompt = ''。this.errorMessage = '',this.currentTab = 'userOptimize',this.selectedApiConfigId,this.selectedTemplateId,});// 🎯计算属性 – 是否正在处理
bool get isProcessing =>
status == OptimizationStatus.loading ||
status == OptimizationStatus.streaming;// 🎯计算属性 – 是否已有结果
bool get hasResult => optimizedPrompt . isNotEmpty;// ✅不可变更新 – 创建新实例
OptimizationState copyWith({
OptimizationStatus?status,String?originalPrompt,String?optimizedPrompt,String?errorMessage,String?currentTab,String?selectedApiConfigId,String?selectedTemplateId,}) {
return OptimizationState(
status : status?,this . status,original Prompt : original Prompt?,this . original Prompt,optimized Prompt :optimized Prompt?,this .optimized Prompt,error Message :error Message?,this .error Message,current Tab :current Tab?,this .current Tab,selected Api Config Id :selected Api Config Id?,话说回来,this .selected Api Config Id。selected Template Id :selected Template Id?,this .selected Template Id,);
} }
关键点 :
// lib/features/optimization/presentation/providers/optimization_provider.dart
class OptimizationNotifier extends StateNotifier<OptimizationState> {
final OptimizePromptUseCase _useCase;final SettingsRepository _settingsRepo;StreamSubscription<String>?_streamSubscription;OptimizationNotifier
: super);// ── Intent :切换 Tab ──
void switchTab{
final templateId=_settingsRepo.getSelectedTemplateI d;state=state.copyWith(
currentTab:type,selectedTemplat eI d:templateI d
);}
// ── Intent :选择 API 配置 ──
void selectApiConfig{
state=state.copyWith;_settingsRepo.saveSelectedA piCon figI d;}
// ── Intent :选择模板 ──
void selectTemplate{
state=state.copyWith;_settingsRepo.saveSelectedTempla teI d;}
// ── Intent :执行调整 ──
Futurea optimize async{
// 🎯 参数校验 — 使用者痛点 #1 :空输入未捕获导致崩溃
if.isEmpty){
state=state.copyW ith(
status的观点是。OptimizationStatu s.error,errorMessage:'🔴 Prompt cannot be empty',);return,}
// 🛑️ Cancel any previous stream to avoid race conditions
await _streamSubscription?.cancel,_streamSubscription=null;// 📈 Update UI → loading
state=state.copyW ith(
status的观点是,OptimizationStat us.loading。originalProm pt:originalPrompt,optimizedPro mpt:'',errorMessag e:'',);try{
final stream=_useCase.execute(
originalProm pt:originalPrompt,apiConfi gI d:state.selecte dApiConfi gI d!,templateI d:state.selecte dTempla teI d!,type:s tate.cur rentTa b,);state=state.copyW ith;_streamSubscription=stream.listen(
{
/// 累积流式内容;只触发一次 State 更新 → 渲染更高效
state=state.cop yWith(
optimizedP rompt:state.optimizedPr ompt+chunk,);},onError:{
state=state.cop yWith(
status的观点是。Optimiza tionStat us.error,errorMessag e:e.toString,);},onDone: async{
/// 自动保存历史记录 — 使用者痛点 #5 :手动保存易忘记
if.isNotEmpty){
await _useCase.saveHistory(
promptOriginal:originalProm pt。promptOptimized:s tate.optimizedPro mpt,apiCo nfigI d:stat e.selec tedA piCo nfigI d!,templa teI d:s tate.sele ctedTemplat eI d!,tabType:s tate.cur rentTa b,);}
/// 最终切换为 success 状态
state=stat e.cop yW ith;},),}catch{
state=stat e.cop yW ith(
从status来看。o ptimizationSt atus.error,errorMessag e:e.toString,);}
}
@override
void dispose{
_streamSubscription?.cancel,super.dispose;不过,}
}
state.errorMessage。UI 可统一展示,无需分散判断。dispose 中统一取消订阅 → 防止内存泄漏,这是对 资源安全 的常用方法。
// Providers 定义区
final dioProvider = Provider<Dio>=>Dio);final openAiApiServiceProvider =
Provider<OpenAiApiService>=>OpenAiApiService));final optimizePromptUseCaseProvider =
Provider<OptimizePromptUseCase>=>OptimizePromptUseCase(
apiService : ref.watch。apiConfigRepo : ref.watch,templateRepo : ref.watch,historyRepo : ref.watch,));/// 主要 StateNotifier Provider —— 自动管理生命周期、支持 override 用于单元测试
final optimizationProvider =
StateNotifierProvider.autoDispose<OptimizationNotifier。OptimizationState>{
return OptimizationNotifier(
ref.watch,ref.watch,);},dependencies:);
autoDispose 自动回收资源——页面离开即销毁 Notifier 与其内部 Stream。
dart
// lib/features/optimization/presentation/pages/home_page.dart
class HomePage extends ConsumerWidget {
@override
Widget build {
final state = ref.watch;final notifier = ref.read;return Scaffold(
说到appBar,AppBar),body: Padding(
padding: const EdgeInsets.all。child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,children:,selected:{state.currentTab},onSelectionChanged:{
notifier.switchTab;},),const SizedBox,
/* ---------- API 配置选择 ---------- */
ApiConfigSelector(
selectedId : state.selectedApiConfigId,onSelected : notifier.selectApiConfig,),const SizedBox,/* ---------- 模板选择 ---------- */
TemplateSelector(
selectedId : state.selectedTemplateId,type : state.currentTab。onSelected : notifier.selectTemplate,),const SizedBox,/* ---------- 输入框 ---------- */
TextField(
decoration const InputDecoration,maxLines :5,onSubmitted :=>notifier.optimize,),const SizedBox,/* ---------- 调整按钮 ----------
* 按钮自动根据 isProcessing 禁用 → 避免重复请求
*/
ElevatedButton(
onPressed:
state.isProcessing?null :
=> notifier.optimize,child:
Text
),const SizedBox。/* ---------- 状态渲染 ----------
根据 Enum 状态分支渲染不同 UI。完全声明式,无任何 if‑else setState。*/
if
const LinearProgressIndicator,if(state.status==OptimizationStatus.streaming ||
state.status==OptimizationStatus.success)
SelectableText。if
Text('❌ ${state.errorMessage}',style:textStyle),],),),
);}
}
:
. 状态一致性 ✔️ vs ❌
dart
// ❌ Traditional Fragmented Flags
bool _isLoading=false;String,_error;
// ... 异步请求后忘记重置其中之一导致 UI 不一致
dart
// ✅ Riverpod + MVI - 单一枚举确保互斥
state = state.copyWith(status:
Optimiza tionStat us.loading);// ,话说回来,later
state = stat e.cop yWi th;
可测性 ✔️ vs ❌
dart
// ❌ 无法直接 mock setS tate
testWidgets;
// ✅ 用 ProviderContainer 注入 Mock 用例即可验证整个流程
final container=
ProviderContainer(overrides:);final noti fier=
container.read;await noti fier.optimize;expect.status,Optimiza tionSta tus.s uccess);
内存安全 ✔️ vs ❌
// ❌ 手动检查 mounted 防止 setS tat e after dispose
if{setS tat e;}
// ✅ autoDispose+dispose 自动回收
@override void dispose{_stre amSubscrip tion?怎么说呢,.cancel;}
// ❌ 一样逻辑散落于 PageA/B/C 各自 StatefulWidget
class PageA extends StatefulWidget {...}
class PageB extends StatefulWidget {...}
// ✅ 同一 Notifier 可被多页共享,只需引用同一 Provider
final optimizationProvider…class PageA extends ConsumerWidget{…}
class PageB extends ConsumerWidget{…}
错误做法 – 每收到一次 chunk 都触发完整 Widget 重建:
dart
stream.listen{
setState=>_result+=chunk);// 整体 rebuild 性能差
});
正确做法 – 把 Chunk 累加到 Immutable State 再让 Riverpod 自动刷新:
dart
_streamSubscription=
stream.listen(
{
state = state.copyWith(
optimizedProm pt:
state.optimizedPro mpt+chunk );},onError:{
state =
stat e.cop yW ith(status:
Optimiza tionStat us.erro r。errorMessa ge:e.t oString);},onDone:{
sta te=
stat e.cop yWi th(sta tus:
Optimiza tionStat us.suc cess );}),
使用 `autoDispose` 或手动覆写 `dispose` 来取消订阅与计时器。话说回来,
dart
class Optimizatio nNotifier extends StateNotif ier<Optimizatio nStat e>{
StreamSubscript ion<String&g t>?_
streamSubscript ion;
@override
void dispose{
_streamSubscript ion?.cancel,super.dispose;}
}
应用被杀后
打开,需要恢复上一次选中的 API 配置与模板。
dart
class Optimizatio nN otifier extends Stat eNotifie r<Optimizati onSta te&g t>{
fin al SettingsRepositor y _
settingsRepo;
Optimizati onN otifier
‑‑–init–‑–
loadPersistedSelections{
final apiConfIg=setting sR epo sitory.ge tSe le ctedAp iCOnfig I dd;fin al tem platE=_setting sRepos itory.ge tSel ectedTe mplate I dd;sta te=
⁞⁞⁞⁞⁞⁞⁞⁞⁞⁞ ⁽₍₎₎ ⁽₍₂₎₎✱✱✱✱✱✱✱✱✨✨✨✨✨✨ ✨ ✨ ✨ ✨ ✨ ✨ 🔥🔥🔥🔥🔥 🔥 🔥 🔥 🔥 🔙🔙🔙⚙️⚙️⚙️⚙️⚙️⚙︎︎︎︎︎︎🟢🟢🟢🟢🟢⬛⬛⬛⬛⬛⬜⬜⬜⬜🌈🌈🌈🌈🌈🏳🏳🏳🚧🚧🚧⏰⏰⏰⏰⏰☕☕☕☕☕🐝🐝🐝🐝🐝🎉🎉🎉🎉🎉🥂🥂🥂🥂🥂💪💪💪💪💪📦📦📦📦📦🚚🚚🚚🚚🚚🔐🔐🔐🔐🔐🍃🍃🍃🍃🍃👻👻👻👻👻❤️❤️❤️❤️❤️🇨🇳🇺🇸🇬🇧🇫🇷🇩🇪 🇭K 🇮T 🇿A 😤😤😤😤😁😁😁😁😂😂😂😂🤔🤔🤔🤔🙄🙄🙄🙄 🙈🙈🙈🙈 🙊 🙊 🙊 🙊 😬😬😬 😭😭😭😭 👋 👋 👋 👋 💔 💔 💔 💔 ⓘⓘⓘⓘ ⓓⓓ ⓖⓖ ⓗⓗ ⓘⓘ ⓒⓨ⌘⌘⌛⌛⌛⌚⌚⇖⇖⇖⇖←←←←→→→→↑↑↑↑↓↓↓↓↔↔↕↕➕➖➗×÷÷×
"
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback