首页 > 解决方案 > 多页表单架构

问题描述

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

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

在此处输入图像描述

我已经设法在ProjectFormContainer中添加MultiPageFormControllerChangeNotifierProvider取得了一些进展,但我不确定如何将单个formPages逻辑与其余元素连接起来,以及为该模型制作体面架构的最佳方法是什么。

我希望你们能给我一些建议。提前致谢!

在此处输入图像描述

标签: flutter

解决方案


这是Bloc State Management的作者Felix Angelov的基于Flow Builder的解决方案。

我使用了以下软件包:

在此处输入图像描述

如果您查看Flow Builder文档,您将看到,它管理一组表单小部件,每个小部件用于 Flow 的每个步骤。完成流程后,它会弹出小部件并返回带有用户信息的主页。

1. 流程的创建

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. 流量管理

在每一步结束时,我们要么继续流程:

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

或者,我们完成它:

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

3. 主页

而且,在我们的主页中,我们导航到OnboardingFlow并等待它完成:

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

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

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;
}


推荐阅读