Flutter动态功能插件dynamic的使用

Flutter动态功能插件dynamic的使用

动态功能插件dynamic

dynamic: 远程UI客户端插件,用于Flutter。

什么是远程UI?

什么是“远程UI”?为什么和什么时候使用它?

远程UI是一种跨平台的UI结构标准,因此你可以以更一致、更完整、更易于维护的方式进行开发。

远程UI的关键特性:

  • 实时反映UI。
  • 一次编写,跨所有平台运行(使用你最喜欢的框架)。
  • 所有数据和逻辑都在服务器端。只发送UI数据。
  • 逻辑集中化。代码更少,更容易维护。
  • 原生性能。(不会影响性能)

远程UI的核心概念

几乎所有应用都会遵循以下步骤:

  1. 应用定义(业务设计)。我们要做什么?为谁做?
  2. 模型定义(数据模式设计)。我们需要获取哪些数据?我们应该向用户提供哪些数据?
  3. UI设计(实际界面设计)。我们如何在屏幕上显示数据?表单将是什么样子?

远程UI是否适合所有人?

不是。 你不需要使用远程UI来制作你的应用。

由于我们的社区很小,你在开发生产级应用时可能会得到较少的支持和更多的麻烦。

它是如何工作的?

让我们通过一个名为“foodle”的食物卡车应用来了解其内部工作原理。

该框架由三层组成:

  • 数据层(存储裸数据的数据层。可以想象为数据库)
  • 布局数据层(将裸数据映射到视图的布局数据)
  • 视图层(绘制布局数据的UI)

数据

var truckData = TruckData(
    name: "gony's burger",
    image: "https://~.jpg",
    shortDescription: "since 1999");

小部件与布局

注意:由于Flutter的小部件命名与远程UI的小部件命名相同,我们选择使用前缀标记“X”。例如,XText是远程UI的文本实例。

var truckItemLayout = (TruckData data) => TruckItemLayout(
    body: TruckItemBody(
        title: RU.Text(data.name),
        description: RU.Text(data.shortDescription),
        image: RU.Image(data.image),
        onTap: RU.Action(null)));

视图

var titleText = (BuildContext context, RU.Text text) => Text(
  text.content,
  style: Theme.of(context).textTheme.body2,
);

var descriptionText = (BuildContext context, RU.Text text) => Text(
  text.content,
  style: Theme.of(context).textTheme.overline,
);

var coverImage = (RU.Image image) => Image.network(
  image.src,
  height: 100.0,
  width: 140.0,
  fit: BoxFit.cover,
);

Function onTapAction = (RU.Action action) => {
  action.handle();
}

var truckItemView =
    (BuildContext context, TruckItemLayout layout) => GestureDetector(
        onTap: onTapAction(layout.body.onTap),
        child: Container(
          padding: EdgeInsets.only(left: 8, right: 8, top: 16, bottom: 16),
          width: 150,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              new ClipRRect(
                borderRadius: new BorderRadius.circular(2.0),
                child: coverImage(layout.body.image)
              ),
              titleText(context, layout.body.title),
              descriptionText(context, layout.body.description),
            ],
          ),
        ));

通过这种方式,你可以动态改变卡片中显示的内容,甚至可以在不更新应用的情况下更改卡片类型。你可以决定当卡片被点击时触发哪个动作。

理论上,如果所有场景都被预先定义好,你将永远不需要更新你的应用来进行应用更新。这将通过更改UI配置来神奇地实现。

如何贡献

该项目仍在开发中,尚未达到测试版状态。

如果你喜欢这个概念,并且喜欢我们正在尝试实现的目标,请随时联系我们进行贡献。

©2020 bridged XYZ. 开发自2019年9月


示例代码

以下是使用dynamic插件的完整示例代码:

import 'package:flutter/material.dart';
import 'package:flutter_remote_ui_example/screen/demo/action/action_demo_screen.dart';
import 'package:flutter_remote_ui_example/screen/demo/foodle/foodle.dart';
import 'package:flutter_remote_ui_example/screen/demo/search_example/search_screen.dart';
import 'package:flutter_remote_ui_example/screen/demo/stile/stile_demo.dart';
import 'package:flutter_remote_ui_example/screen/demo/timeline/timeline_demo_screen.dart';
import 'package:flutter_remote_ui_example/screen/wallet_screen.dart';
import 'package:flutter_remote_ui_example/screen/demo/youtube/youtube.dart';
import 'package:flutter_remote_ui_example/utils/routes.dart';

import 'screen/icons_demo/icons_demo_screen.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // 这个小部件是你的应用程序的根。
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'remote-ui',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: DemoHomePage.routeName,
      routes: buildRoute(context),
    );
  }
}

const List<DemoData> demos = [
  const DemoData(title: "remote icons", route: IconsDemoScreen.routeName),
  const DemoData(
      title: "Foodle",
      route: Foodle.routeName,
      cover: 'https://blog.luz.vc/wp-content/uploads/2019/01/food-truck-ou-food-container-2-696x356.jpg'),
  const DemoData(
      title: "Wallet",
      route: WalletDemo.routeName,
      cover: "https://www.loveworldplus.tv/wp-content/uploads/2019/04/bank.jpg"),
  const DemoData(
      title: "Youtube",
      route: YoutubeDemo.routeName,
      cover: "https://marketingland.com/wp-content/ml-loads/2017/08/youtube-logo-1920-800x450.jpg"),
  const DemoData(title: "Stile", route: StileDemo.routeName),
  const DemoData(title: "Timeline", route: TimelineDemoScreen.routeName),
  const DemoData(title: "Search results", route: SearchScreen.routeName),
  const DemoData(title: "Actions", route: ActionDemoScreen.routeName),
];

class DemoHomePage extends StatefulWidget {
  static const routeName = "/home";

  [@override](/user/override)
  _DemoHomePageState createState() => _DemoHomePageState();
}

class _DemoHomePageState extends State<DemoHomePage> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("select demo"),
        ),
        body: SingleChildScrollView(
          child: Column(
            children: [demoListBuilder()],
          ),
        ));
  }

  Widget demoListBuilder() {
    return ListView.builder(
      itemBuilder: (c, i) {
        var demo = demos[i];
        return DemoRow(
          title: Text(demo.title),
          cover: demo.cover != null
              ? Image.network(
                  demo.cover,
                  width: double.maxFinite,
                  fit: BoxFit.fitWidth,
                )
              : null,
          onTap: () {
            Navigator.of(context).pushNamed(demo.route);
          },
        );
      },
      itemCount: demos.length,
      shrinkWrap: true,
      physics: NeverScrollableScrollPhysics(),
    );
  }
}

class DemoData {
  final String cover;
  final String title;
  final String route;

  const DemoData({this.cover, [@required](/user/required) this.title, [@required](/user/required) this.route});
}

class DemoRow extends StatelessWidget {
  final Function onTap;
  final Widget title;
  final Widget cover;

  const DemoRow({Key key, this.onTap, this.title, this.cover}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return GestureDetector(
        onTap: onTap,
        child: Container(
          width: double.maxFinite,
          height: 200,
          child: Stack(
            children: [
              this.cover != null
                  ? this.cover
                  : Container(
                      color: Colors.blueAccent,
                    ),
              Positioned(left: 24, bottom: 24, child: _title(context))
            ],
          ),
        ));
  }

  Widget _title(BuildContext context) {
    return DefaultTextStyle(
      child: this.title,
      style: Theme.of(context).textTheme.headline2.copyWith(color: Colors.white),
    );
  }
}

更多关于Flutter动态功能插件dynamic的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter动态功能插件dynamic的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,动态功能插件(dynamic features)通常涉及将应用拆分为多个模块(modules),以便按需加载这些模块,从而提高应用的性能和用户体验。虽然Flutter本身并没有直接提供名为“dynamic”的官方插件,但你可以通过Flutter的模块化和插件系统来实现动态加载功能。

以下是一个基本的示例,展示如何在Flutter应用中创建和使用动态模块。

步骤 1: 创建主应用

首先,创建一个新的Flutter项目作为主应用。

flutter create main_app
cd main_app

步骤 2: 创建动态模块

main_app项目目录中,使用以下命令创建一个新的Flutter模块,这将作为你的动态模块。

flutter create -t module dynamic_feature

这将在main_app项目目录中创建一个名为dynamic_feature的子目录,其中包含你的动态模块代码。

步骤 3: 配置动态模块

dynamic_feature模块的pubspec.yaml中,你可以定义该模块所需的依赖项。例如:

name: dynamic_feature
description: A new Flutter module.
version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

# Add any other dependencies your module needs

步骤 4: 实现动态模块功能

dynamic_feature/lib/main.dart中,你可以定义该模块提供的功能。例如,一个简单的屏幕:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Dynamic Feature Module'),
        ),
        body: Center(
          child: Text('Hello from the dynamic feature module!'),
        ),
      ),
    );
  }
}

步骤 5: 从主应用加载动态模块

main_app项目中,你需要使用flutter_engineplatform_channels等机制来加载和显示动态模块的内容。这通常涉及到一些平台特定的代码(如Android的Activity和iOS的ViewController),以及Flutter的插件系统。

由于直接展示完整的加载过程涉及较多代码和配置,这里提供一个简化的思路:

  1. 在Android/iOS平台上配置动态模块加载:你需要确保动态模块被正确打包到APK/IPA中,并且可以在运行时被加载。

  2. 使用Flutter的插件或通道与原生代码通信:从主应用发送命令到原生代码,要求加载动态模块,并在加载完成后显示其内容。

  3. 在Flutter中处理模块加载后的逻辑:使用Navigator或类似的机制将动态模块的内容嵌入到主应用的UI中。

示例代码(简化)

以下是一个简化的示例,展示如何在Flutter中通过平台通道请求加载动态模块(注意:这只是一个概念验证,实际实现会更复杂):

main_app/lib/main.dart

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  static const platform = MethodChannel('com.example.dynamic_feature');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Main App'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () async {
              try {
                await platform.invokeMethod('loadDynamicFeature');
                // Handle success (e.g., navigate to the loaded feature)
              } on PlatformException catch (e) {
                // Handle error
                print("Failed to load dynamic feature: '${e.message}'.");
              }
            },
            child: Text('Load Dynamic Feature'),
          ),
        ),
      ),
    );
  }
}

在Android平台上MainActivity.ktMainActivity.java中),你需要实现loadDynamicFeature方法,该方法负责加载并显示动态模块的内容。

由于篇幅限制,这里无法展示完整的Android和iOS实现代码,但你可以参考Flutter官方文档和社区资源来了解如何实现这些功能。

总结

虽然Flutter没有直接提供一个名为“dynamic”的插件来实现动态功能,但你可以通过模块化、平台通道和原生代码集成来实现类似的功能。上述示例提供了一个基本的框架,你可以在此基础上根据具体需求进行扩展和完善。

回到顶部