Basics

NyState

简介

NyState 是标准 Flutter State 类的扩展版本。它提供了额外的功能,帮助您更高效地管理页面和组件的状态。

您可以像使用普通 Flutter state 一样与之交互,同时享受 NyState 带来的额外好处。

让我们来了解如何使用 NyState。

如何使用 NyState

您可以通过继承此类来开始使用。

示例

class _HomePageState extends NyState<HomePage> {

  @override
  get init => () async {

  };

  @override
  view(BuildContext context) {
    return Scaffold(
        body: Text("The page loaded")
    );
  }

init 方法用于初始化页面的状态。您可以使用带有 async 或不带 async 的方式使用此方法,框架会在后台处理异步调用并显示加载器。

view 方法用于显示页面的 UI。

使用 NyState 创建新的有状态组件

要在 Nylo Website 中创建新的有状态组件,您可以运行以下命令。

metro make:stateful_widget ProfileImage

加载样式

您可以使用 loadingStyle 属性为页面设置加载样式。

示例

class _ProfileImageState extends NyState<ProfileImage> {

  @override
  LoadingStyleType get loadingStyle => LoadingStyleType.normal();

  @override
  get init => () async {
    await sleep(3); // simulate a network call for 3 seconds
  };

默认的 loadingStyle 将使用您的加载组件(resources/widgets/loader_widget.dart)。 您可以自定义 loadingStyle 来更新加载样式。

以下是您可以使用的不同加载样式表:

样式 描述
normal 默认加载样式
skeletonizer 骨架屏加载样式
none 无加载样式

您可以这样更改加载样式:

@override
LoadingStyle get loadingStyle => LoadingStyle.normal();
// or
@override
LoadingStyle get loadingStyle => LoadingStyle.skeletonizer();

如果您想更新某个样式中的加载组件,可以传递一个 childLoadingStyle

@override
LoadingStyle get loadingStyle => LoadingStyle.normal(
    child: Center(
        child: Text("Loading..."),
    ),
);
// same for skeletonizer
@override
LoadingStyle get loadingStyle => LoadingStyle.skeletonizer(
    child: Container(
        child: PageLayoutForSkeletonizer(),
    )
);

现在,当标签页加载时,将显示文本"Loading..."。

以下是示例:

class _HomePageState extends NyState<HomePage> {
    get init => () async {
        await sleep(3); // simulate a network call for 3 seconds
    };

    @override
    LoadingStyle get loadingStyle => LoadingStyle.normal(
        child: Center(
            child: Text("Loading..."),
        ),
    );

    @override
    Widget view(BuildContext context) {
        return Scaffold(
            body: Text("The page loaded")
        );
    }
    ...
}

状态操作

在 Nylo 中,您可以在组件中定义小型操作,这些操作可以从其他类中调用。当您需要从另一个类更新组件的状态时,这非常有用。

首先,您必须在组件中定义您的操作。这适用于 NyStateNyPage

class _MyWidgetState extends NyState<MyWidget> {

  @override
  get init => () async {
    // handle how you want to initialize the state
  };

  @override
  get stateActions => {
    "hello_world_in_widget": () {
      print('Hello world');
    },
    "update_user_name": (User user) async {
      // Example with data
      _userName = user.name;
      setState(() {});
    },
    "show_toast": (String message) async {
      showToastSuccess(description: message);
    },
  };
}

然后,您可以使用 stateAction 方法从另一个类调用该操作。

stateAction('hello_world_in_widget', state: MyWidget.state);

// Another example with data
User user = User(name: "John Doe");
stateAction('update_user_name', state: MyWidget.state, data: user);
// Another example with data
stateAction('show_toast', state: MyWidget.state, data: "Hello world");

如果您将 stateActions 与 NyPage 一起使用,则必须使用页面的 path

stateAction('hello_world_in_widget', state: ProfilePage.path);

// Another example with data
User user = User(name: "John Doe");
stateAction('update_user_name', state: ProfilePage.path, data: user);

// Another example with data
stateAction('show_toast', state: ProfilePage.path, data: "Hello world");

还有另一个名为 StateAction 的类,它有一些方法可以用来更新组件的状态。

  • refreshPage - 刷新页面。
  • pop - 弹出页面。
  • showToastSorry - 显示抱歉提示通知。
  • showToastWarning - 显示警告提示通知。
  • showToastInfo - 显示信息提示通知。
  • showToastDanger - 显示危险提示通知。
  • showToastOops - 显示糟糕提示通知。
  • showToastSuccess - 显示成功提示通知。
  • showToastCustom - 显示自定义提示通知。
  • validate - 验证组件中的数据。
  • changeLanguage - 更新应用中的语言。
  • confirmAction - 执行确认操作。

示例

class _UpgradeButtonState extends NyState<UpgradeButton> {

  view(BuildContext context) {
    return Button.primary(
      onPressed: () {
        StateAction.showToastSuccess(UpgradePage.state,
          description: "You have successfully upgraded your account",
        );
      },
      text: "Upgrade",
    );
  }
}

只要组件是状态管理的,您可以使用 StateAction 类来更新应用中任何页面/组件的状态。

辅助方法

lockRelease
showToast isLoading
validate afterLoad
afterNotLocked afterNotNull
whenEnv setLoading
pop isLocked
changeLanguage confirmAction
showToastSuccess showToastOops
showToastDanger showToastInfo
showToastWarning showToastSorry

Reboot

此方法将重新运行状态中的 init 方法。当您想刷新页面上的数据时很有用。

示例

class _HomePageState extends NyState<HomePage> {

  List<User> users = [];

  @override
  get init => () async {
    users = await api<ApiService>((request) => request.fetchUsers());
  };

  @override
  Widget view(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Users"),
          actions: [
            IconButton(
              icon: Icon(Icons.refresh),
              onPressed: () {
                reboot(); // refresh the data
              },
            )
          ],
        ),
        body: ListView.builder(
          itemCount: users.length,
          itemBuilder: (context, index) {
            return Text(users[index].firstName);
          }
        ),
    );
  }
}

Pop

pop - 从堆栈中移除当前页面。

示例

class _HomePageState extends NyState<HomePage> {

  popView() {
    pop();
  }

  @override
  Widget view(BuildContext context) {
    return Scaffold(
      body: InkWell(
        onTap: popView,
        child: Text("Pop current view")
      )
    );
  }

showToast

在上下文中显示提示通知。

示例

class _HomePageState extends NyState<HomePage> {

  displayToast() {
    showToast(
        title: "Hello",
        description: "World",
        icon: Icons.account_circle,
        duration: Duration(seconds: 2),
        style: ToastNotificationStyleType.INFO // SUCCESS, INFO, DANGER, WARNING
    );
  }

  @override
  Widget view(BuildContext context) {
    return Scaffold(
      body: InkWell(
        onTap: displayToast,
        child: Text("Display a toast")
      )
    );
  }

validate

validate 辅助方法对数据执行验证检查。

您可以在此处了解更多关于验证器的信息。

示例

class _HomePageState extends NyState<HomePage> {
TextEditingController _textFieldControllerEmail = TextEditingController();

  handleForm() {
    String textEmail = _textFieldControllerEmail.text;

    validate(rules: {
        "email address": [textEmail, "email"]
      }, onSuccess: () {
      print('passed validation')
    });
  }

changeLanguage

您可以调用 changeLanguage 来更改设备上使用的 json /lang 文件。

此处了解更多关于本地化的信息。

示例

class _HomePageState extends NyState<HomePage> {

  changeLanguageES() {
    await changeLanguage('es');
  }

  @override
  Widget view(BuildContext context) {
    return Scaffold(
      body: InkWell(
        onTap: changeLanguageES,
        child: Text("Change Language".tr())
      )
    );
  }

whenEnv

您可以使用 whenEnv 在应用处于特定状态时运行函数。 例如,您的 .env 文件中的 APP_ENV 变量设置为 'developing',APP_ENV=developing

示例

class _HomePageState extends NyState<HomePage> {

  TextEditingController _textEditingController = TextEditingController();

  @override
  get init => () {
    whenEnv('developing', perform: () {
      _textEditingController.text = 'test-email@gmail.com';
    });
  };

lockRelease

此方法将在函数被调用后锁定状态,只有当方法完成后才允许用户发出后续请求。此方法还会更新状态,使用 isLocked 来检查。

展示 lockRelease 的最佳示例是想象我们有一个登录屏幕,当用户点击"登录"时,我们想执行异步调用来登录用户,但我们不希望该方法被多次调用,因为这可能导致不好的体验。

以下是示例。

class _LoginPageState extends NyState<LoginPage> {

  _login() async {
    await lockRelease('login_to_app', perform: () async {

      await Future.delayed(Duration(seconds: 4), () {
        print('Pretend to login...');
      });

    });
  }

  @override
  Widget view(BuildContext context) {
    return Scaffold(
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (isLocked('login_to_app'))
              AppLoader(),
            Center(
              child: InkWell(
                onTap: _login,
                child: Text("Login"),
              ),
            )
          ],
        )
    );
  }

一旦您点击 _login 方法,它将阻止任何后续请求,直到原始请求完成。isLocked('login_to_app') 辅助方法用于检查按钮是否被锁定。在上面的示例中,您可以看到我们用它来确定何时显示加载组件。

isLocked

此方法将使用 lockRelease 辅助方法检查状态是否被锁定。

示例

class _HomePageState extends NyState<HomePage> {

  @override
  Widget view(BuildContext context) {
    return Scaffold(
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (isLocked('login_to_app'))
              AppLoader(),
          ],
        )
    );
  }

view

view 方法用于显示页面的 UI。

示例

class _HomePageState extends NyState<HomePage> {

  @override
  Widget view(BuildContext context) {
      return Scaffold(
          body: Center(
              child: Text("My Page")
          )
      );
  }
}

confirmAction

confirmAction 方法将向用户显示一个对话框以确认操作。 当您希望用户在继续之前确认操作时,此方法很有用。

示例

_logout() {
 confirmAction(() {
    // logout();
 }, title: "Logout of the app?");
}

showToastSuccess

showToastSuccess 方法将向用户显示成功提示通知。

示例

_login() {
    ...
    showToastSuccess(
        description: "You have successfully logged in"
    );
}

showToastOops

showToastOops 方法将向用户显示糟糕提示通知。

示例

_error() {
    ...
    showToastOops(
        description: "Something went wrong"
    );
}

showToastDanger

showToastDanger 方法将向用户显示危险提示通知。

示例

_error() {
    ...
    showToastDanger(
        description: "Something went wrong"
    );
}

showToastInfo

showToastInfo 方法将向用户显示信息提示通知。

示例

_info() {
    ...
    showToastInfo(
        description: "Your account has been updated"
    );
}

showToastWarning

showToastWarning 方法将向用户显示警告提示通知。

示例

_warning() {
    ...
    showToastWarning(
        description: "Your account is about to expire"
    );
}

showToastSorry

showToastSorry 方法将向用户显示抱歉提示通知。

示例

_sorry() {
    ...
    showToastSorry(
        description: "Your account has been suspended"
    );
}

isLoading

isLoading 方法将检查状态是否正在加载。

示例

class _HomePageState extends NyState<HomePage> {

  @override
  Widget build(BuildContext context) {
    if (isLoading()) {
      return AppLoader();
    }

    return Scaffold(
        body: Text("The page loaded", style: TextStyle(
          color: colors().primaryContent
        )
      )
    );
  }

afterLoad

afterLoad 方法可用于在状态完成"加载"之前显示加载器。

您还可以使用 loadingKey 参数检查其他加载键 afterLoad(child: () {}, loadingKey: 'home_data')

示例

class _HomePageState extends NyState<HomePage> {

  @override
  get init => () {
    awaitData(perform: () async {
        await sleep(4);
        print('4 seconds after...');
    });
  };

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: afterLoad(child: () {
          return Text("Loaded");
        })
    );
  }

afterNotLocked

afterNotLocked 方法将检查状态是否被锁定。

如果状态被锁定,它将显示 [loading] 组件。

示例

class _HomePageState extends NyState<HomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
          alignment: Alignment.center,
          child: afterNotLocked('login', child: () {
            return MaterialButton(
              onPressed: () {
                login();
              },
              child: Text("Login"),
            );
          }),
        )
    );
  }

  login() async {
    await lockRelease('login', perform: () async {
      await sleep(4);
      print('4 seconds after...');
    });
  }
}

afterNotNull

您可以使用 afterNotNull 在变量被设置之前显示加载组件。

假设您需要使用 Future 调用从数据库获取用户账户,这可能需要 1-2 秒,您可以在该值上使用 afterNotNull,直到获取到数据。

示例

class _HomePageState extends NyState<HomePage> {

  User? _user;

  @override
  get init => () async {
    _user = await api<ApiService>((request) => request.fetchUser()); // example
    setState(() {});
  };

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: afterNotNull(_user, child: () {
          return Text(_user!.firstName);
        })
    );
  }

setLoading

您可以使用 setLoading 切换到"加载中"状态。

第一个参数接受一个 bool 表示是否正在加载,下一个参数允许您为加载状态设置名称,例如 setLoading(true, name: 'refreshing_content');

示例

class _HomePageState extends NyState<HomePage> {

  @override
  get init => () async {
    setLoading(true, name: 'refreshing_content');

    await sleep(4);

    setLoading(false, name: 'refreshing_content');
  };

  @override
  Widget build(BuildContext context) {
    if (isLoading(name: 'refreshing_content')) {
      return AppLoader();
    }

    return Scaffold(
        body: Text("The page loaded")
    );
  }