多页面表单架构 [英] Multi Page Form Architecture

查看:61
本文介绍了多页面表单架构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试构建一个系统来创建具有多个页面的表单.我的方法是将其分为三个不同的部分.

  1. FormPages :不同的表单页面(每个页面都有自己的逻辑来验证字段).
  2. ProjectFormContainer :在导航器中保存页面的容器页面.
  3. MultiPageFormController :用于管理表单页面之间导航的控制器.

我已设法在 MultiPageFormController ChangeNotifierProvider 中添加到 ProjectFormContainer 中,但是我不确定如何连接单独的 formPages 逻辑以及其余元素,以及为该模型构建体面架构的最佳方法是什么.

我希望你们能给我一些建议.预先感谢!

解决方案

以下是基于

如您所见,如果您查看 Flow Builder 文档,它会管理一系列Form Widget,该流程的每一步都需要一个.完成流程后,将弹出窗口小部件,并返回带有用户信息的主页.

1.流的创建

  FlowBuilder< UserInfo>(状态:const UserInfo(),onGeneratePages :(配置文件,页面){返回 [MaterialPage(子级:NameForm()),如果(profile.name!= null)MaterialPage(child:AgeForm()),如果(profile.age!= null)MaterialPage(child:ColorForm()),];},), 

2.流管理

在每个步骤的最后,我们要么继续执行流程:

 上下文.flow< UserInfo>().update((info)=> info.copyWith(name:_name.value)); 

或者,我们完成它:

 上下文.flow< UserInfo>().complete((info)=> info.copyWith(favoriteColor:_color.value)); 

3.主页

然后,在我们的主页中,导航到 OnboardingFlow 并等待其完成:

  _userInfo.value =等待Navigator.of(context).push(OnboardingFlow.route()); 


完整的源代码,易于复制粘贴

  import'package:flow_builder/flow_builder.dart';导入'package:flutter/material.dart';导入'package:flutter_hooks/flutter_hooks.dart';导入'package:freezed_annotation/freezed_annotation.dart';导入'package:flutter_colorpicker/flutter_colorpicker.dart';部分'66228603.flow_builder.freezed.dart';void main(){runApp(MaterialApp(debugShowCheckedModeBanner:否,标题:流程演示",主页:HomePage(),),);}//主页类HomePage扩展了HookWidget {@override窗口小部件build(BuildContext context){final _userInfo = useState< UserInfo>();返回脚手架(backgroundColor:_userInfo.value == null?颜色.白色:_userInfo.value.favoriteColor,appBar:AppBar(标题:Text('Flow')),身体:容器(填充:EdgeInsets.all(8.0),对齐方式:Alignment.center,子代:_userInfo.value == null?升高按钮(onPressed:()异步{_userInfo.value =等待Navigator.of(context).push(OnboardingFlow.route());},子代:文字('GET STARTED'),): 柱子(mainAxisAlignment:MainAxisAlignment.center,孩子们: [文本(欢迎光临,$ {_ userInfo.value.name}!",样式:TextStyle(fontSize:48.0),),const SizedBox(高度:48.0),文本(所以,您今年$ {_ userInfo.value.age}岁,这是您最喜欢的颜色?伟大的!',样式:TextStyle(fontSize:32.0),),],),),);}}//流类OnboardingFlow扩展了StatelessWidget {静态Route< UserInfo>路线() {return MaterialPageRoute(builder:(_)=> OnboardingFlow());}@override窗口小部件build(BuildContext context){print('INFO:$ {const UserInfo()}');返回脚手架(正文:FlowBuilder< UserInfo>(状态:const UserInfo(),onGeneratePages :(配置文件,页面){返回 [MaterialPage(子级:NameForm()),如果(profile.name!= null)MaterialPage(child:AgeForm()),如果(profile.age!= null)MaterialPage(child:ColorForm()),];},),);}}//形式类NameForm扩展了HookWidget {@override窗口小部件build(BuildContext context){final _name = useState< String>();返回脚手架(appBar:AppBar(标题:const Text('Name')),身体:容器(填充:EdgeInsets.all(8.0),对齐方式:Alignment.center,子:列(mainAxisAlignment:MainAxisAlignment.center,子代:< Widget> [文本域(自动对焦:是的,onChanged :(值)=>_name.value =值,装饰:InputDecoration(labelText:'名称',hintText:'输入您的名字',),),const SizedBox(高度:24.0),加高按钮(子级:const Text('Continue'),onPressed:(){如果(_name.value.isNotEmpty){语境.flow< UserInfo>().update((info)=> info.copyWith(name:_name.value));}},)],),),);}}AgeForm类扩展了HookWidget {@override窗口小部件build(BuildContext context){final _age = useState< int>();返回脚手架(appBar:AppBar(标题:const Text('Age')),身体:容器(填充:EdgeInsets.all(8.0),对齐方式:Alignment.center,子:列(mainAxisAlignment:MainAxisAlignment.center,子代:< Widget> [DropdownButtonFormField< int>(项目:List.generate(200,(索引)=>DropdownMenuItem(值:索引,子级:Text(index.toString()),),),onChanged :(值)=>_age.value =值,装饰:InputDecoration(labelText:'年龄',hintText:你几岁?",),),const SizedBox(高度:24.0),加高按钮(子级:const Text('Continue'),onPressed:(){如果(_age.value!= null){语境.flow< UserInfo>().update((info)=> info.copyWith(age:_age.value));}},)],),),);}}类ColorForm扩展了HookWidget {@override窗口小部件build(BuildContext context){final _color = useState< Color>(Colors.amber);返回脚手架(appBar:AppBar(标题:const Text('Favorite Color')),身体:容器(填充:EdgeInsets.all(8.0),对齐方式:Alignment.center,子:列(mainAxisAlignment:MainAxisAlignment.center,子代:< Widget> [颜色选择器(pickerColor:_color.value,onColorChanged :(值)=>_color.value =值,showLabel:是的,pickerAreaHeightPercent:0.8,),const SizedBox(高度:24.0),加高按钮(子级:const Text('Continue'),onPressed:(){如果(_color.value!= null){context.flow< UserInfo>().complete((信息)=>info.copyWith(favoriteColor:_color.value));}},)],),),);}}//领域@freezed_ $ UserInfo {const factory UserInfo({字符串名称,整数年龄,颜色favoriteColor})=_用户信息;} 

I'm trying to build a system to create forms with multiples pages. My approach was to separate it in three different parts.

  1. FormPages: The different form pages (Each one will have its own logic to validate fields).
  2. ProjectFormContainer: The container page that holds the pages inside a Navigator.
  3. MultiPageFormController: A controller to manage the navigation between form pages.

I've managed to make some progress adding to the ProjectFormContainer a ChangeNotifierProvider of MultiPageFormController but I'm not sure how to connect the individual formPages logic with the rest of the elements and what's the best way to make a decent architecture for this model.

I hope you guys could give me some advice. Thanks in advance!

解决方案

Here is a Solution based on Flow Builder from Felix Angelov, the author of Bloc State Management.

I used the following packages:

As you will see if you check Flow Builder documentation, it manages a deck of Form Widgets, one for each step of the Flow. Once it completes the Flow, it pops the Widgets out and gets back to the Main Page with the User Info.

1. Creation of the Flow

FlowBuilder<UserInfo>(
  state: const UserInfo(),
  onGeneratePages: (profile, pages) {
    return [
      MaterialPage(child: NameForm()),
      if (profile.name != null) MaterialPage(child: AgeForm()),
      if (profile.age != null) MaterialPage(child: ColorForm()),
    ];
  },
),

2. Flow Management

At the end of each step, we either continue the flow:

context
 .flow<UserInfo>()
 .update((info) => info.copyWith(name: _name.value));

Or, we complete it:

context
  .flow<UserInfo>()
  .complete((info) => info.copyWith(favoriteColor: _color.value));

3. Main Page

And, in our Main Page, we navigate to the OnboardingFlow and await for it to complete:

_userInfo.value = await Navigator.of(context).push(OnboardingFlow.route());


Full source code for easy copy-paste

import 'package:flow_builder/flow_builder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';

part '66228603.flow_builder.freezed.dart';

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flow Demo',
      home: HomePage(),
    ),
  );
}

// MAIN PAGE

class HomePage extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final _userInfo = useState<UserInfo>();
    return Scaffold(
      backgroundColor: _userInfo.value == null
          ? Colors.white
          : _userInfo.value.favoriteColor,
      appBar: AppBar(title: Text('Flow')),
      body: Container(
        padding: EdgeInsets.all(8.0),
        alignment: Alignment.center,
        child: _userInfo.value == null
            ? ElevatedButton(
                onPressed: () async {
                  _userInfo.value =
                      await Navigator.of(context).push(OnboardingFlow.route());
                },
                child: Text('GET STARTED'),
              )
            : Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    'Welcome, ${_userInfo.value.name}!',
                    style: TextStyle(fontSize: 48.0),
                  ),
                  const SizedBox(height: 48.0),
                  Text(
                    'So, you are ${_userInfo.value.age} years old and this is your favorite color? Great!',
                    style: TextStyle(fontSize: 32.0),
                  ),
                ],
              ),
      ),
    );
  }
}

// FLOW

class OnboardingFlow extends StatelessWidget {
  static Route<UserInfo> route() {
    return MaterialPageRoute(builder: (_) => OnboardingFlow());
  }

  @override
  Widget build(BuildContext context) {
    print('INFO: ${const UserInfo()}');
    return Scaffold(
      body: FlowBuilder<UserInfo>(
        state: const UserInfo(),
        onGeneratePages: (profile, pages) {
          return [
            MaterialPage(child: NameForm()),
            if (profile.name != null) MaterialPage(child: AgeForm()),
            if (profile.age != null) MaterialPage(child: ColorForm()),
          ];
        },
      ),
    );
  }
}

// FORMS

class NameForm extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final _name = useState<String>();
    return Scaffold(
      appBar: AppBar(title: const Text('Name')),
      body: Container(
        padding: EdgeInsets.all(8.0),
        alignment: Alignment.center,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextField(
              autofocus: true,
              onChanged: (value) => _name.value = value,
              decoration: InputDecoration(
                labelText: 'Name',
                hintText: 'Enter your name',
              ),
            ),
            const SizedBox(height: 24.0),
            RaisedButton(
              child: const Text('Continue'),
              onPressed: () {
                if (_name.value.isNotEmpty) {
                  context
                      .flow<UserInfo>()
                      .update((info) => info.copyWith(name: _name.value));
                }
              },
            )
          ],
        ),
      ),
    );
  }
}

class AgeForm extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final _age = useState<int>();
    return Scaffold(
      appBar: AppBar(title: const Text('Age')),
      body: Container(
        padding: EdgeInsets.all(8.0),
        alignment: Alignment.center,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            DropdownButtonFormField<int>(
              items: List.generate(
                200,
                (index) => DropdownMenuItem(
                  value: index,
                  child: Text(index.toString()),
                ),
              ),
              onChanged: (value) => _age.value = value,
              decoration: InputDecoration(
                labelText: 'Age',
                hintText: 'How old are you?',
              ),
            ),
            const SizedBox(height: 24.0),
            RaisedButton(
              child: const Text('Continue'),
              onPressed: () {
                if (_age.value != null) {
                  context
                      .flow<UserInfo>()
                      .update((info) => info.copyWith(age: _age.value));
                }
              },
            )
          ],
        ),
      ),
    );
  }
}

class ColorForm extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final _color = useState<Color>(Colors.amber);
    return Scaffold(
      appBar: AppBar(title: const Text('Favorite Color')),
      body: Container(
        padding: EdgeInsets.all(8.0),
        alignment: Alignment.center,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ColorPicker(
              pickerColor: _color.value,
              onColorChanged: (value) => _color.value = value,
              showLabel: true,
              pickerAreaHeightPercent: 0.8,
            ),
            const SizedBox(height: 24.0),
            RaisedButton(
              child: const Text('Continue'),
              onPressed: () {
                if (_color.value != null) {
                  context.flow<UserInfo>().complete(
                      (info) => info.copyWith(favoriteColor: _color.value));
                }
              },
            )
          ],
        ),
      ),
    );
  }
}

// DOMAIN

@freezed
abstract class UserInfo with _$UserInfo {
  const factory UserInfo({String name, int age, Color favoriteColor}) =
      _UserInfo;
}

这篇关于多页面表单架构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆