Fluent component framework

Time:2021-11-26

Please indicate the source and keep the article complete:
Fluent component framework

Fluent component framework

cover.png

Componentization is everywhere

There are “military Division brigade regiment battalion” in the army. The battalion is a component of the regiment and the division is a component of the army.

There are “provinces, cities, counties and villages” in the country. The village is the component of the township and the city is the component of the province.

To manage complex structures, it is a good method to divide components layer by layer.

The componentization discussed in this paper is not for the purpose of code reuse.

This paper focuses on managing complex projects through componentization to improve human efficiency and reduce loss.

problem

Complex projects often have the following two main problems:

  1. Large amount of code, slow compilation.
  2. The logic is complex and difficult to maintain.

Solution ideas

break up the whole into parts

When it comes to code style, many people know this common sense.

If a function is too long, it needs to be split into different functions.

If a class is too long, it needs to be split into different classes.

If a file is too long, it needs to be split into different files.

Similarly, if our project is too large and there are too many files, we need to split it into different components.

Reduce coupling

After being disassembled into different components, each component is still inextricably linked, and maintenance will still be messy.

We should try to avoid dependencies between components. Reduce coupling.

The use of router can well solve the coupling caused by page jumping to each other.

Later, we will show how to reduce coupling through routing in fluent.

How to organize your code Army

I’ve seen a set of Android code “make full use of MVP mode” before.
The whole project is divided into three directories, m, V and P.
V is divided into activities, views, adapters

Although the logic and UI are very separated, to modify a business or add a new set of functions, you need to add code one by one across various directories.

I recommend the following division methods:

+------------------------------------------------+
|                    main frame                  |
|  +-----------+-----------+-----------+-----+   |
|  | business1 | business2 | business3 | ... |   |
+--+-----------+-----------+-----------+-----+---+
+------------------------------------------------+
|                   common                       |
+------------------------------------------------+

Business1, business2, business3,… Are different business modules.

For example: Gallery, search, course, recommendation

The advantage of this is that when you add code, you can focus on the current business,
Ignoring other business modules greatly reduces the scope of code to be maintained.

Generally, the development and change of product requirements are also carried out in business units.
For example, the product manager came up with a new course model, which will affect some code related to the course.
If our code has already encapsulated the course business, the modifications will be carried out inside this module. Without connecting other parts of the code.

Of course, we don’t ignore the reuse of code. Usually, there are a lot of code worthy of reuse that has nothing to do with business.
It has nothing to do with business, that is, it does not belong to a business. Business a may be used, and business B may also be used.
These reusable business independent code will be the common part of the architecture above the matrix.

To sum up:

  1. Business modules are the most basic code division to isolate business changes within corresponding components.
  2. The parts that both the main framework and the business module depend on are put into the public library. (there are business independent codes in the Public Library)

Practice in fluent

Flutter is naturally suitable for componentization. As long as you study deeply enough, you can find these characteristics:

  1. The built-in tools can create independently running modules
  2. Built in routing support (router)
  3. You can distinguish between app and component running environment (commandline Env) through command line environment variables

Here I wrote a set of framework examples:Flutterame

Naming meaning: fluttername = flutter + frame

prepare

Sharp tools make good work.

Let’s get ready first

1. Create project

flutter create –org top.ovo –platforms ios,android flutterame

If you want to learn more about the use of fluent create, please see another article:Play with the fluent create command and be a 10x programmer
In addition to my translation of fluent create — help, there are some other strange tips.

2. Optimize Android compilation speed for domestic networks

2.1 manually modify the Android / build.gradle file.

Before modification:

    repositories {
        google()
        jcenter()
    }

After modification:

    repositories {
        maven { url 'https://maven.aliyun.com/repository/google'} // google()
        maven { url 'https://maven.aliyun.com/repository/jcenter'} // jcenter()
    }

2.2 script modification

bin/optimize_cn_network.py android/

For details of network speed optimization, see:Android compilation and network speed optimization of fluent project

Well, now you can have a pleasant flight run.

After the preparations are completed, let’s create a component.

Start implementing componentization

3. Create components

If we want to create a module named Gallery in the modules / directory of the project, we can first enter the modules directory and then create the module with fluent create

flutter create -t module --org top.ovo gallery

The – t module here means that you want to create a component instead of a complete app.

The created component directory is as follows:

.
├── .android
├── .dart_tool
├── .gitignore
├── .ios
├── .metadata
├── .packages
├── README.md
├── build
├── gallery.iml
├── gallery_android.iml
├── lib
│   └── main.dart
├── pubspec.lock
├── pubspec.yaml
└── test

3.1 components can run directly

In the current component directory, we can directly run this component with fluent run.
Soon you will see the familiar counter demo page.
It is not much different from an ordinary app.

How the newly created component runs

It can be seen that the developers of fluent have fully considered the needs of componentization. You can not only run components in the app, but also run components separately.

3.2 magical “. Android” and “. IOS”

If you look closely at the created directory, you will find an interesting phenomenon:

Android and IOS directories are missing from the app. Instead, there are two hidden directories “. Android” and “. IOS”.

In other words, these two directories are hidden and will not be added to git by default.
If someone else pulls this set of code, can you run this component alone?

Through experiments, it is found that:

If you run fluent run in a component directory without “. Android” and “. IOS” directories.

Fluent will recreate the “. Android” and “. IOS” directories.

Because the. Android directory is code generated, all the changes you make in it may be discarded at any time.
So in myAndroid compilation and network speed optimization of fluent projectThe article also introduces that the script automatically optimizes the network speed to avoid the trouble of manual modification every time.

After creating the component, it’s time to use it. Next, let’s see how to use the component code.

3.3 embed your component interface in the app.

3.3.1 create component interface

First, we create a simple component interface, Galleria, and store it in the component’s galleria.dart file

modules/gallery/lib/gallria.dart:

import 'package:flutter/material.dart';

class Galleria extends StatelessWidget {
  const Galleria();

  @override
  Widget build(BuildContext context) {
    return Center(child: Text("Galleria"));
  }
}
3.3.2 add dependency on components in app

To call the code in the component, our app needs to declare the dependency on the component:

We can modify pubspec.yaml to complete this declaration

Before modification:

dependencies:
  flutter:
    sdk: flutter

After modification:

dependencies:
  flutter:
    sdk: flutter
  gallery:
    path: modules/gallery

Emphasize that because our components are currently local, we need to usePath: + relative directoryDeclare this dependency.

After modification, you can execute: fluent pub get to complete the installation.

If you use vscode, you will automatically execute ‘fluent pub get’ after modifying pubspect.yaml.

3.3.3 classes in referenced components in app

Import the modules / gallery / lib / galleria.dart file from the app:

import 'package:gallery/galleria.dart';
import 'package:flutterame/mine.dart';


//...
const TabItems = {
  Tabitem. Galleria: {'icon': icons. School, 'title': 'gallery', 'widget': galleria()},
  Tabitem. Mine: {'icon': icons.more_horiz, 'title': 'my', 'widget': mine()}
};
//...

We added the bottom tab in our app and used Galleria () to create the controls in the component.

It is not difficult to find that the hierarchy of components is independent rather than embedded in the app.

import 'package:gallery/galleria.dart'; // This has nothing to do with flutterame.
import 'package:flutterame/mine.dart';

This is also good for reusing component code in the future.

So far, we have completed the coarsest componentized example app.

Come on, show

When we run “fluent run” in the module directory, we will see the results of the component running alone.

When we run “fluent run” in the project root directory, we will see the running results of the whole app.

Run in component Run in app

But it’s not over

I added a JSON file to the component as data for display.

As a result, when I run the plug-in, everything is normal.

But an error occurred when I ran the whole app.

4. Add resources to the component

The story goes like this:

First, create the data directory under the component directory and add the resource file:

modules/gallery/datas/gallery_source.json

.
├── .android
├── .ios
├── datas
│   └── gallery_source.json
├── lib
├── pubspec.lock
├── pubspec.yaml
└── test

Then modify modules / gallery / pubspec.yaml and add the assets field:

Before modification:


flutter:
  uses-material-design: true

  # To add Flutter specific assets to your application, add an assets section,
  # like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

After modification:


flutter:
  uses-material-design: true

  assets:
    - datas/gallery_source.json

Use defaultassetbundle to reference resources in Code:

@override
  Widget build(BuildContext context) {
    return FutureBuilder(
        future: DefaultAssetBundle.of(context)
            .loadString('datas/gallery_source.json'),
        builder: (context, snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.none:
            case ConnectionState.waiting:
              return Center(child: Text('Loading...'));
            default:
              if (snapshot.hasError)
                return Center(child: Text('Error: ${snapshot.error}'));
              else {
                //If_ Calculation execution completed normally
                const json = const JsonCodec();
                var data = json.decode(snapshot.data.toString());
                return _buildLoaded(data);
              }
          }
        });
  }

4.1 here comes the problem

When I start the component, I can see the correct results, but I report an error when I run the app to display the page in the component.

Run in component Run in app

Why?

4.2 exploration results

After exploration, it is found that the paths of resources in app and components are different after they are packaged.

Resources in app: packages / gallery / data / Gallery_ source.json
Resources in component: data / Gallery_ source.json

Therefore, we can find the resource file in the plug-in, but we can’t find it when running in the app.

The process of explorationIt’s not complicated.

Find the installation package of the app and plug-in and unzip it.

Installation package file structure of the whole app:
build/app/outputs/apk/debug/app-debug.apk:

.
├── AndroidManifest.xml
├── META-INF
├── assets
│   └── flutter_assets
│       ├── AssetManifest.json
│       ├── FontManifest.json
│       ├── NOTICES
│       ├── fonts
│       ├── isolate_snapshot_data
│       ├── kernel_blob.bin
│       ├── packages
│       │   ├── cupertino_icons
│       │   └── gallery
│       │       └── datas
│       │           └── gallery_source.json
│       └── vm_snapshot_data
├── classes.dex
├── kotlin
├── lib
├── project.txt
├── res
└── resources.arsc

Single component installation package file structure:
modules/gallery/build/host/outputs/apk/app.apk:

.
├── .DS_Store
├── AndroidManifest.xml
├── META-INF
├── assets
│   ├── .DS_Store
│   └── flutter_assets
│       ├── AssetManifest.json
│       ├── FontManifest.json
│       ├── NOTICES
│       ├── datas
│       │   └── gallery_source.json
│       ├── fonts
│       ├── isolate_snapshot_data
│       ├── kernel_blob.bin
│       ├── packages
│       └── vm_snapshot_data
├── classes.dex
├── lib
├── res
└── resources.arsc

4.3 solutions

After consulting several articles, I finally found a solution.

Use commandargument + bool.fromenvironment to distinguish the environment.

Originally, the fromenvironment function can not only judge whether it is a production environment, such as:

final isProd = const bool.fromEnvironment('dart.vm.product');

You can also determine the parameters passed to the shuttle run command through the command line.

The usage is as follows:

4.3.1 passing custom parameters on the command line:

When you start a component, you not only use fluent run, but also pass parameters to it

flutter run --dart-define=IS_RUN_ALONE=true
4.3.2 judge in the code:
class EnvironmentConfig {
  static const IS_RUN_ALONE =
      bool.fromEnvironment('IS_RUN_ALONE', defaultValue: false);
}
4.3.3 load different resources according to different environments
  @override
  Widget build(BuildContext context) {
    const isRunAlone = EnvironmentConfig.IS_RUN_ALONE;
    return FutureBuilder(
        future: DefaultAssetBundle.of(context).loadString(isRunAlone
            ? 'datas/gallery_source.json'
            : 'packages/gallery/datas/gallery_source.json'),
        builder: (context, snapshot) {
         //...
        });
  }

This is finally perfect:

Run in component Run in app

After verification, this scheme can also work normally on IOS.

4.3.4 reference articles:

Flutter 1.17 — no more Flavors, no more iOS Schemas. Command argument that changes everything

bool.fromEnvironment

String.fromEnvironment

int.fromEnvironment

5. How to decouple

After more than ten years of trial and error, it is found that the following two points can effectively reduce coupling:

  1. Avoid referencing the code in the framework from components.
  2. Minimize references to code in components from the framework.

You can easily do the first point, as long as you don’t add dependency on fluttername in your gallery module.
If there is some code that really needs to be shared between flutterame and gallery, consider putting it into a common library.
Then add the dependency on the public library in the fluttername and gallery.

To achieve the second point, you need to consider whether each import ‘package: Gallery / *. Dart’ in each fluttername is necessary.

for instance:
If you want to jump to a page in gallery in fluttername, aboutgallery.

Of course you can write that directly

Navigator.push(context, MaterialPageRoute(builder: (_) {
            return new AboutGallery();
          }));

Question:

In this way, in order to create aboutgallery, you also need to import files where this jump is called

import 'package:gallery/about.dart';

resolvent:

Using route, if we use pushnamed instead, we can jump by route without importing widget

    // Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => GalleryAbout()));
    Navigator.pushNamed(context, "/gallery/about");

However, using routing is not so simple, and some preparations need to be done.

5.1 creating routes in components

Add the file routes.dart in the component modules / gallery / lib /

import 'package:flutter/material.dart';
import 'package:gallery/main.dart';
import 'package:gallery/detail.dart';
import 'package:gallery/about.dart';

class GalleryRouteGenerator {
  //Configure routing
  static final routes = {
    "/": (context, {arguments}) = > myhomepage (Title: 'gallery'),
    "/gallery/detail": (context, {arguments}) => Detail(gallery: arguments),
    "/gallery/about": (context, {arguments}) => GalleryAbout(),
  };

  static Route<dynamic> generateRoute(RouteSettings settings) {
    final String name = settings.name;
    final Function pageContentBuilder = routes[name];
    if (pageContentBuilder != null) {
      final Route route = MaterialPageRoute(
          builder: (context) =>
              pageContentBuilder(context, arguments: settings.arguments));
      return route;
    } else {
      return _ Errorpage ('page not found ');
    }
  }

  static Route _errorPage(msg) {
    return MaterialPageRoute(builder: (_) {
      return Scaffold(
          AppBar: AppBar (Title: text ('unknown page '), body: Center (child: text (MSG));
    });
  }
}

5.2 using routing in components

In the component modules / gallery / lib / main.dart, import the file routes.dart:

import 'package:gallery/routes.dart';
//...
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Module: gallery'),
      initialRoute: '/',
      onGenerateRoute: GalleryRouteGenerator.generateRoute,
    );
  }
  //...
}

So far, the routing in the component is working.

If you want to use routing in the app, you need to do a little work.

5.3 add component routing in app

Add routes.dart in the Lib / directory of app:

import 'package:flutter/material.dart';
import 'package:flutterame/about.dart';
import 'package:flutterame/home.dart';

class RouteGenerator {
  //Configure routing
  static final routes = {
    "/": (context, {arguments}) = > myhomepage (Title: 'gallery'),
    "/about": (context, {arguments}) => About(),
  };

  static Route<dynamic> generateRoute(RouteSettings settings) {
    final String name = settings.name;
    final Function pageContentBuilder = routes[name];
    if (pageContentBuilder != null) {
      final Route route = MaterialPageRoute(
          builder: (context) =>
              pageContentBuilder(context, arguments: settings.arguments));
      return route;
    } else {
      return _ Errorpage ('page not found ');
    }
  }

  static Route _errorPage(msg) {
    return MaterialPageRoute(builder: (_) {
      return Scaffold(
          AppBar: AppBar (Title: text ('unknown page '), body: Center (child: text (MSG));
    });
  }
}

Add a component route to this file:

//...
import 'package:gallery/routes.dart';

class RouteGenerator {
  //Configure routing
  static final routes = {
    ...GalleryRouteGenerator.routes,
    "/": (context, {arguments}) = > myhomepage (Title: 'gallery'),
    "/about": (context, {arguments}) => About(),
  };
  //...
}

… the syntax galleryroutegenerator.routes, which I often use in reactnative, runs with the idea of trying, but it works. Happy

These three points mean that the following objects (galleryroutegenerator. Routes) are disassembled and merged into a new object (routegenerator. Routes).

5.4 using component routing in app

Invoked in lib/mine.dart file:

Navigator.pushNamed(context, "/gallery/about")

The navigator will take “/ gallery / about” to the app route to find the widget. Find it and jump to that page.

After work, the complete code is on GitHub:Flutterame

It’s the show again

Use routing in components

Let’s go to modules / gallery / startup component first:

flutter run --dart-define=IS_RUN_ALONE=true

When we click on the picture cover, we will enter the details page. Click the “about” button in the lower right corner to enter the about page of the component, and “about Gallery” is displayed.

Use routing in app

Next, we go back to the project root directory and start the whole app through fluent run.

Click the picture cover in the first tab to enter the details page in the component. Here, the hero component unique to flutter is used to realize cross screen animation. I like it

Click “about” in the second tab to enter the about page of the app.

Click “Gallery about” in the second tab to enter the about page of the component.

Run in component Run in app
[image upload failed… (image-cbb0f9-1610028963990)] [image upload failed… (image-1e0a7b-1610028963990)]

⭐️ ⭐️⭐️⭐️ ⭐️
⭐️

I’m looking forward to your praise
⭐️
⭐️ The author of this article is looking forward to your forwarding, praise and attention ⭐️

Recommended Today

SQL exercise 20 – Modeling & Reporting

This blog is used to review and sort out the common topic modeling architecture, analysis oriented architecture and integration topic reports in data warehouse. I have uploaded these reports to GitHub. If you are interested, you can have a lookAddress:https://github.com/nino-laiqiu/TiTanI recorded a relatively complete development process in my hexo blog deployed on GitHub. You can […]