This article is by IMWeb IMWeb team
preface
Tencent’S OED client team launched Flutter into the Penguin Tutoring business in the first half of 2019. This year we went to The Google Developer Conference 2019 in Shanghai together and met more Flutter developers. The experience felt much more familiar than the first time we went. I hope to invite them to Shenzhen in the future to share some Flutter technology. This developer conference also coincides with the official incorporation of Flutter to Web into Master. So, can the front-end students take advantage of this opportunity to participate in the collaborative development of Flutter? I think this question will trouble many people. If you have a good idea, you can join us in the comments section.
This article is not a detailed tutorial on Flutter, but rather an overview, describing Flutter in as simple and contrasting terms as possible. In line with the perspective of the front-end students, to understand a new technology, but not limited to the front-end technology itself. Hopefully, this article will give you a relatively comprehensive understanding of Flutter itself. I am particularly grateful for the encouragement and support of the leader, who gave me the opportunity to learn and understand the Flutter framework, because compared to me, the classmates in the OED client team have far more experience than me. They have gone through the whole process of business from 0 to 1, which was a very interesting experience.
Flutter Technical Architecture
Why Flutter?
Before we talk about Flutter, let’s briefly review the client’s last technological innovation, React Native (RN). It is believed that many teams have the opportunity to practice RN. The first screen rendering scheme of many apps is carried out by RN technology stack. The same goes for our own penguin Tutoring and Tencent in-class apps.
Just to recap, why does RN appear in the context of client development?
RN’s value is simply acceptable page performance + efficient development + hot updates.
Update: After the traditional APP is put on the shelves, there are business bugs, and users can only update the APP to fix the bugs. Client implementation of hot updates to fix bugs, how difficult it is, you can ask IOS development students. High probability guess, hand Q and wechat, there should be a scheme can be hot update. But for a lot of small vendors it’s really tough. RN’s value, therefore, is perfectly justified by its ability to be dynamic.
Efficiency: When an APP is released and launched, Android and IOS need to develop two applications at the same time, while RN only needs one set of code to run on both platforms, saving a lot of labor costs. In addition, many business lines have strong business operation demands, and there may be multiple revisions and releases in a very short period of time. The human bottleneck of client development and the limitation of release cycle make it difficult to meet such business scenarios. Especially in the case of some bad releases, the performance of traditional clients in the case of running out of time with bugs and subsequent incremental fixes can be disastrous.
Performance: RN has a better performance experience than H5. After all, page rendering is done through the client, which is much faster than WebView rendering. This is reflected in Weex, Hippy, although lower than Native performance, but in the acceptable range.
PS: The expression here is not to describe poor client development. One of the core values of an architect is that it is important to put the right technology in the right place while looking at things purely from a business perspective.
So if we look back at these three things, we see that there’s some inevitability about RN. So back to the topic, why should THERE be a Flutter when RN is already so excellent? When I asked Brother Ab about the development of technology, Brother Ab mentioned an interesting point that the depth of your understanding of a technology depends on whether you can recognize the limitations of this technology. Like a person, he or she has as many faults as he or she has merits. Not finding it doesn’t mean it doesn’t exist, because it must exist. So, with that in mind, let’s just look briefly at RN.
First, look at maintenance costs, even though RN is a set of code that runs on multiple ends. But we still need IOS and Android development to help us draw components one by one, especially when we meet special demands, and also need to deal with case by case. And with the iteration and upgrade of IOS and Android system itself, as well as the historical burden of the framework’s own development, We might also have to deal with a lot of platform differences from native systems and fix all sorts of weird bugs, which is a huge burden on the business.
Secondly, the pursuit of performance, whether for products or developers, for user experience, will never stop. RN has many shortcomings in performance, which is why Weex is designed to solve problems in business scenarios. Communication between JS and Native, event monitoring of pages, and rendering and exchange costs of complex animations are all big performance challenges.
Therefore, when there is a stronger business appeal, people have to find a better way to achieve it. We are very grateful to look at Google, which is positioned as a commercial company, but in fact there is a huge gap between companies in terms of their influence on the world. The scope of this topic is too large, we can discuss it in depth when we have the opportunity later.
As you can see from the description above, in order to solve these problems, the era of self-drawing engines has emerged, and the technological solutions represented by the Flutter will emerge. I believe that the Flutter will not be the only technology to emerge. After all, the history of Flutter is surprisingly similar. In fact, when I think of a self-drawing engine, the first thing that comes to mind are those game engines. So why present the concept of a self-drawing engine? While H5 relies on browser rendering, RN relies on client rendering, and Flutter is based on Skia’s own drawn graphical interface. Therefore, Flutter can truly achieve cross-end! It is believed that Flutter will be available on traditional clients in the near future, so that it can truly achieve multi-endpoint unification. Think of a word ~ train of thought decides outlet.
Finally, let’s briefly summarize the problems:
1. Poor Web performance, visible gap with native App;
2. React Native supports very limited capabilities compared with Web, requiring three-end teams to deal with specific long scenarios one by one.
3. Android fragmentation of Web browsers is severe (thanks X5, Tencent students have a relatively easy life).
To solve the above problem, Flutter appears:
One set of code can run on both ends
A custom UI, separate from the platform, can also simply be thought of as a subset of the browser.
The purpose of this introduction is to remind you of the development and trends of technology and to better understand the following presentation. Now we formally introduce Flutter.
Principle of Flutter Implementation
There are many technologies that Flutter can introduce. Here are some representative technologies to share with you based on my own understanding. This includes design ideas, rendering methods, and UI lifecycle. Because these points are very similar to the React stack style, this kind of thinking structure can help people better understand the technology itself.
Design of the overall Architecture of Flutter
Google keywords, search for this image, and describe it from the bottom up:
Embedder: An operating system Embedder layer that implemented platform-specific features such as rendering Surface Settings, thread Settings, and platform plug-ins. From this we can see that the Flutter platform does not have many characteristics, which makes the cost of maintaining cross-end consistency from the framework level relatively low.
The Flutter Engine is a pure C++ SDK that includes the Skia Engine, Dart runtime, text typography Engine, and more. It is simply a Dart runtime that can run Dart code in JIT, JIT Snapshot, or AOT mode. Dart: UI library Native Binding implementation is provided when code calls dart: UI library. Don’t forget, however, that this runtime also controls VSync signal passing, GPU data filling, and so on, and is responsible for passing client-side events to the code in the runtime. The specific method of drawing will be described later.
The Flutter Framework: This is a pure Dart implementation SDK, similar to what React does in JavaScript. It implements a basic set of libraries for handling animations, drawings, and gestures. We’ve packaged a library of UI components based on graphics, and we’ve differentiated them according to the Material and Cupertino visual styles. The SDK for this pure Dart implementation is packaged into a Dart library called Dart: UI. When we use Flutter to write apps, we import this library directly to use functions such as components.
- The Framework layer is the most relevant layer for developers. Here are the modules:
- Foundation: At the lowest level, it mainly defines the underlying utility classes and methods for use by other layers.
- Animation: Is an animator-related class that can be used to create Tween Animation and physics-based Animation, similar to Android ValueAnimator and iOS Core Animation.
- Painting: Encapsulates the drawing interfaces provided by the Flutter Engine, such as drawing scaled images, interpolating shadows, drawing box model borders, etc.
- Gesture: Provides processing for Gesture recognition and interaction
- Rendering: is the Rendering library in the frame. Control rendering consists of three stages: Layout, Paint, and Composite.
PS: Although I have known about Flutter for a long time, I have actually written about Flutter for a short time. Engine source level, there is no in-depth dabbling. You can read the source code yourself, or ask Google, Alibaba, Meituan, and our developers for help. Understanding this from a technical point of view, at the stage of need, will not be a bottleneck for everyone. After all, the business world is full of barriers, and the technology itself at the application level is open.
Flutter application layer language
- A brief description of JIT and AOT:
JIT is used during the development cycle to deliver and execute code dynamically. The development test efficiency is high, but the running speed and execution performance are affected by the just-in-time compilation.
AOT is pre-compilation, which can generate binary code that is directly executed. It runs fast and performs well. However, it needs to be compiled in advance before each execution, resulting in low development and testing efficiency.
- What is the Dart
It aims to be the next generation of structured Web development language. Dart was announced at Google’s GOTO International Software Development Conference in October 2011. Is a class-based programming language that runs in all browsers with high performance. The Dart VM is built into Chrome to run the Dart code directly and efficiently (removed in 2015). Support for Dart code to be converted to Javascript and run directly on the Javascript engine. dart2js
- The characteristics of the Dart
JIT during development to improve development efficiency; Release AOT to improve performance. There will be no interaction between JS and Native. Dart’s memory strategy, which uses a multi-generation algorithm (somewhat similar to Node). The threading model is still a single-threaded Event Loop model, which is isolated through isolate to make development easier (very similar to Node). The Dart ecosystem, which is far from Node.js, is still the most active in the industry. Static syntax and typography, pure front-end entry is still a cost.
Remarks :(1) TS can help JS to add some static detection to a certain extent, but it still cannot achieve such effect in essence; (2) On the question of entry cost, if you want to go deeper, I believe this will not be an issue. The key is whether it brings value to the business and the team.
- Why Flutter chose Dart
Robust type system that supports both static type checking and runtime type checking. Tree Shaking code volume, only code that needs to be called at runtime is preserved at compile time (no implicit references such as reflection are allowed), so a large library of Widgets does not make publishing too big. Rich underlying libraries, Dart itself provides a lot of libraries. Multi-generation lockless garbage collector, optimized for creating and destroying the large number of Widgets objects common in UI frameworks. Cross-platform, iOS and Android share the same code. JIT & AOT operation mode supports rapid iteration at development time and maximum hardware performance after release. Native Binding. On Android, v8’s Native Binding works well, but JavaScriptCore on iOS does not, so if JavaScript is used, the code pattern of the Flutter base framework is not consistent. Dart Native Binding can be implemented with Dart Lib.
Realization of Flutter
Having seen the introduction above, here is a summary of the implementation of Flutter. It broke new ground in design, implemented a true cross-platform solution, developed its own UI framework, its rendering engine was implemented by the Skia graphics library, and the development language was Dart, which supports both JIT and AOT. It not only ensures the development efficiency, but also improves the execution efficiency. Due to the implementation of Flutter’s self-drawn UI, there is as little variation between platforms as possible. Also maintain the same high performance as native apps. Therefore, Flutter is also the most flexible and thorough cross-platform development solution, rewriting the underlying rendering logic and the entire solution of the upper development language.
-
Complete span:
- Flutter builds a complete development suite that includes both low-level rendering and top-level design.
- This not only ensures a high degree of consistency in view rendering on Android and IOS, but also ensures rendering and interaction performance comparable to native apps.
-
Core differences with existing schemes:
- RN scheme, JS development, Native rendering. Data communication Bridge;
- Hybird browser rendering + native component rendering;
- Flutter is designed to be self-closed loop for rendering and data communication.
UI rendering scheme
When it comes to UI rendering solutions, as front-end development, we can’t get around the three frameworks that are in full swing right now. Why do we talk about React like scenarios? Because the Design of Flutter had the same idea as the React design. In rendering we cover controls, rendering principles, and life cycles.
How does Flutter render pages? Whereas traditional Web is through a browser, Flutter is self-drawn. Self-drawing refers to the ability of Flutter to draw itself to the user interface, without relying on the native capabilities of Ios and Android. The page drawing of Flutter is done using a Skia engine.
Introduce Skia
Skia is a 2D drawing engine library, the predecessor of vector drawing software, used by Chrome and Android as a drawing engine Skia. Skia provides a very friendly API and provides friendly and efficient performance in graphics conversion, text rendering, and bitmap rendering. Skia is cross-platform, so it can be embedded into the iOS SDK of Flutter without having to explore iOS closed source Core Graphics/Core Animation.
Skia is a 2D image drawing engine developed by C++ with strong performance. Its predecessor is a vector drawing software. Skia excelled in graphics conversion, text rendering, bitmap rendering, and provided a developer-friendly API. Android comes with Skia, so the Flutter Android SDK is much smaller than the iOS SDK. Thanks to Skia:
- The rendering capability of the underlying Flutter has been unified and no longer needs to be used for two-end adaptation.
- With OpenGL and GPU, there is no need to rely on the native component rendering framework.
- Flutter is designed to minimize the differences between platforms and improve rendering efficiency and performance.
The rendering process of Flutter
A user can see an image presentation that requires at least three media: CPU, GPU, and display. CPU is responsible for image data calculation, GPU is responsible for image data rendering, and the display is the carrier of the final picture display. The CPU takes the data that needs to be displayed on the screen for processing and processing, and then delivers it to the GPU. The GPU puts the data into the frame buffer after rendering, and then reads the data from the buffer with the control synchronization signal (VSync) at a periodic frequency to present the image on the display. And the operating system is an infinite loop mechanism, constantly repeat the above operation, for the update of the display.
The overall rendering process of Flutter is the same. Dart synthesizes view data and then hands it to the Skia engine for processing, which then hands it to the GPU for data synthesis and preparation for display. When one frame of an image is drawn and ready to draw the next, the display emits a vertical sync signal, or VSync, so a 60Hz screen emits 60 such signals a second.
Flutter drawing process
As shown above, the Flutter rendering process is divided into 7 steps:
The first thing is to get the user’s actions, and then your application will display some animation for that
Flutter then starts building Widget objects.
After the Widget object is built, it enters the rendering phase, which consists of three steps:
- Layout elements: Determine the position and size of page elements on the screen;
- Draw stage: Draw page elements as they should be;
- Synthesis stage: The products of the previous two steps are combined according to the drawing rules
The final rasterization is done by the Engine layer.
layout
The layout of Flutter depth first traverses the rendered object tree. Data flows pass constraints from top to bottom and sizes from bottom to top. That is, the parent node passes its constraints to its children, who calculate their size based on the constraints they receive and then return their size to the parent node. In the whole process, the location information is controlled by the parent node, the child node does not care about its location, and the parent node does not care about the specific appearance of the child node.
To prevent the whole control tree from being redrawn when factor nodes change, Flutter incorporates a mechanism called Relayout Boundary. In certain situations, the Relayout Boundary is automatically created without manual addition by the developer.
Boundaries: Flutter uses boundaries to mark nodes that need to be rearranged and redrawn so that other nodes do not become contaminated or trigger a rebuild. When control size does not affect other controls, there is no need to rearrange the entire control tree. With this mechanism, no matter what changes happen to the subtree, the scope of processing is only in the subtree.
Caching: To improve performance, caching is also essential. In a Flutter, almost all elements will have a key. This key is unique. When the subtree is rebuilt, only the different parts of the key are refreshed. The reuse of node data relies on keys to retrieve it from the cache.
After the location and size of each space is determined, the drawing stage is entered. When you draw nodes, you walk through the tree of nodes in depth, and then draw different RenderObjects on different layers.
draw
After the layout is complete, each node in the rendered object tree has a definite size and location. Flutter draws all the elements onto different layers. Similar to the layout process, the process of drawing is depth-first traversal, drawing the parent node first and then the child node. For example: nodes 1, 2, 3, 4, 5, and finally, node 6.
As you can see in the figure above, for example, views may merge, causing node 2’s child node 5 to be on the same layer as its sibling node 6. This will cause unrelated node 6 to be redrawn when node 2 needs to be redrawn, causing performance problems.
To solve the above problems, Flutter proposed a mechanism of layout Boundary — repaint-boundary. Within the redrawn boundary, Flutter forces new layers to be changed. This avoids the interaction between inside and outside the boundary, and avoids unnecessary redrawing of unrelated content even though it is at the same level.
A typical scenario for redrawing boundaries is a ScrollView. ScorllView refreshes the view as it scrolls, triggering content redrawing, and while scrolling content is redrawn, other content generally does not need to be redrawn. This is where redrawing the boundaries becomes very valuable.
The idea here is to be more refined and have minimal control over component updates.
Composition and rendering
Flutter’s 7-layer Rendering pipline is shown above. This is mainly to describe the understanding of composition and rendering. The rendering pipeline is driven by Vsync signals. The concept is similar to the concept of an FPS, 60 frames per second, too low and the page will appear to be jammed.
After each Vsync signal arrives, the Flutter framework performs a series of actions in the sequence shown in the diagram:
Animate, Build, Layout and Paint
Finally, a Scene is generated and sent to the bottom layer to be drawn on the screen by GPU.
The Flutter App only needs to trigger the rendering pipeline when the state changes. Flutter does not need to re-render pages when there is no state change in your App. Therefore, Vsync signals need to be scheduled by the Flutter App. For example, we use the setState method in the Widget to change the state of the control.
The entire rendering pipeline runs in the UI thread, driven by Vsync signals, and outputs the Layer Tree after the frame is rendered. The Layer Tree is sent to Engine, which schedules the Layer Tree to GPU thread, synthesizes (compsite) Layer Tree in GPU thread, and then sends the Layer Tree to GPU for display after rendering by Skia 2D rendering Engine. Layer Tree is mentioned here because the final output of the rendering pipeline phase we are going to analyze is this Layer Tree. So the drawing phase is not as simple as calling the Paint function, but a lot of the Layer Tree management is involved.
Flutter only cares about providing view data to the GPU. The GPU’s VSync signal is synchronized to the UI thread. The UI thread uses Dart to construct an abstract view structure. This data is fed to the GPU via OpenGL or Vulkan.
Here to describe synthesis, the concept of the so-called synthesis is because our drawing page structure is complex, if direct delivery to graphics rendering engine to layer, there may be a lot of rendering content to redraw, therefore, need to advance a layer synthesis, means that all of the first layer according to the size and level rules to calculate the final show effect, Combine the same layers to simplify the rendering tree and improve rendering efficiency.
Flutter gives the resultant data to Skia to render the two-dimensional layers of the page.
What do you think of when you see a Widget?
The basis of the Flutter drawing interface is the Widget, the smallest module that describes the page.
-
The core design idea of Flutter is that “Everything is a Widget” :
-
Think of a Widget as a Component of a Web Component.
-
An abstraction of structured data, including component layout, render properties, event response information, and so on.
-
In this section we compare React design to the implementation of Flutter. In React you can see three important names. JSX, Virtual Dom and real Dom. In Flutter, we can still see the corresponding three abstract data structures, namely Widget, Element and RenderObject. Their functions are similar to those of the three data abstractions in React.
The Widget is similar to the x in F(x) = Y of the React VM
The widgets in Flutter are completely immutable! Whenever the view changes, Flutter creates a new Widget to update. React also has a certain data Diff strategy. However, changing the creation method will bring a lot of destruction and reconstruction. Does it cost a lot of performance?
The object of the Widget is the data description JSX of the virtual DOM node that identifies React, not the actual rendered page DOM. It’s just an abstraction of data, not a view rendering. The immutability of widgets also improves the reusability of widgets themselves. So there is no significant performance cost, and Dart’s performance as a static language is faster than JS’s.
Element is an instantiated object of a Widget
Element holds the context data for view construction and is the bridge between structured configuration information and final rendering. Element is a mutable data structure, roughly known as the Virtual DOM. Diff updates are available; The data that actually needs to be modified can be synchronized to the RenderObject. Minimize rendering view modification and improve rendering efficiency.
RenderObject Is the object responsible for rendering a view
The rendering of Flutter is divided into 4 parts. Layout, drawing, composition, and rendering, where layout and drawing are done in RenderObject. Flutter renders an object tree in a depthwise manner. It determines the position and size of each object in the tree and draws it on different layers. When the drawing is complete, it is handed to Skia to synthesize bitmaps from the tree when the VSync signal is synchronized.
Widgets are also classified as stateful and stateless components
The stateless control StatelessWidget is similar to the React PFC. The stateful control StatefulWidget is a component of React. As with the React component, there is a cost to using stateful components. Evaluate your requirements properly and avoid meaningless stateful components.
The big difference here is that Flutter directly makes the Widget immutable! This also leads to differences in the implementation of technical solutions.
Now that you see widgets, there must be a life cycle
Every time you see the terms component, state, and view, there is a concept called lifecycle. Yes, Flutter also has a life cycle.
The component lifecycle is divided into three phases:
create
Constructor –> initState –> didChangeDependencies –> build
State: construction method is the starting point of life cycle, the Flutter will pass StatefulWidget. CreateState to create a State. We can use the initialization method to receive the initialization UI configuration parameters passed by the parent Widget that determine the initial configuration of the Widget
InitState: is called when the State object is inserted into the view tree. This function is called only once during the lifetime of the State, so we can do some initialization here, such as setting default values for State variables.
DidChangeDependencies: handles State object dependencies that are called by a Flutter after initSate().
Build: Builds the view. Through the above steps, the Framework decides that Sate is ready and calls Build. In this function, we need to create a Widget based on the initial configuration data passed in by the parent Widget and the current State of State, and then return it.
update
Status updates of widgets are triggered by three methods: setState, didChangeDependencies, and didUpdateWidget.
SetState: is the most familiar way of updating within the control and then building from scratch.
DidChangeDependencies: State Calls back to this method when the dependencies of the object change, which triggers component builds. In what cases does the dependency of a State object change? Typically, Sate is told to execute the didChangeDependencies callback method when the system language Locale or application theme changes.
DidUpdateWidget: This function is called when the configuration of the Widget changes, for example, when the parent Widget triggers a rebuild (that is, when the state of the parent Widget changes), or when the Widget is hot overloaded.
Once these three functions are called, Flutter then destroys the old Widget and calls the Build method to rebuild the Widget.
The destruction
Component destruction is relatively simple. When a component is removed or a page is destroyed, the system calls diactivate and Dispose methods to remove or dispose the component.
When the component’s visible state changes, the deactivate function is called and Sate is temporarily removed from the view tree. Note that this function is also called when the State object changes position in the view tree during a page switch and needs to be temporarily removed and then added again to trigger a component build.
Dispose function is called when a State object is permanently removed from the view tree. Once at this stage, the component is destroyed, so this is where we can do the final resource release, remove the listener, and clean up the environment.
APP Life cycle
From the background to the foreground, the console prints the changes of App life cycle as follows:
AppLifecycleState. Paused – > AppLifecycleState. Inactive – > AppLifecycleState. Resumed;
From the foreground to the background, the console prints the App life cycle changes as follows:
AppLifecycleState. Resumed – > AppLifecycleState. Inactive – > AppLifecycleState. Paused;
For a more detailed explanation of the Flutter lifecycle, you can Google the Flutter lifecycle. The Engineers at Meituan also have an example on Github
A brief overview of the basic aspects of Flutter in the experiential learning process, most of which have already been experienced and practiced through code. I won’t go through them one by one here. Here are three examples to share with you, you can clone down and experience with the details:
Layout case: github.com/yang7229693…
Code example: github.com/nisrulz/flu…
FlutterDemo: github.com/OpenFlutter…
Beyond what I’ve listed above, there’s a lot more to do, such as operations, debugging, automated testing, compatibility, client SDK encapsulation, internationalization, and more. Of course for developers, engineering, debugging experience, componentization are very important.
Flutter to Web test
The code I’m converting here is the business code in our APP. The detailed operation process of the Flutter official documentation is well explained. If you simply turn to a project that is completely independent of APP, it is estimated that you can use the environment after installing it. Converting code from a commercial product will still involve dealing with some strange problems, which should not be a problem for you. You can configure the environment here by installing and configuring the environment. The Website of Flutter provides an example you can try. Officials have given an out-of-the-box development document.
The video above shows the Flutter business in the third TAB of our Penguin tutorial and the transformed Web page. Obviously, there is some distortion in one part, but it is perfectly usable. Here the page rendering has a part of Canvas rendering and DOM filling to display the page. Dart naturally supported use in Chrome and has long supported conversion to JavaScript. Therefore, we can expect that the evolution of Dart To Js business practices will outpace the use of WASM in the future as Flutter develops.
Here are some issues to address, compiled from official advice and practical experience:
First of all, it is not recommended for use in productization, but now that it is integrated into Master, I believe this day will not be far away.
When business use, need to solve the system dependency, such as: local storage, network requests and so on. For example, the client uses WNS and the front-end uses HTTPS. For businesses that rely heavily on 3D animation, don’t choose Flutter as a business option in the short term. Flutter to Web can be used as a business disaster recovery strategy in the future.
citation
Flutter website
Because Chinese website
Flutter of actual combat
Idle fish technology blog post
conclusion
Thank you again for the precipitation of predecessors. There are really too many things to learn. The actual articles I want to write are much more than those described in this article, but I have to cut a lot of content due to the high time cost. Later, with a deeper understanding of Flutter, I will have the opportunity to share with you the internal design principles of Flutter in more detail. I am just a porter of knowledge. As a developer in the field of application layer, the greatest value is to serve good products and meet product demands with technology to the maximum extent. As for the development and construction of the bottom of the core, the degree of life can be achieved depends on fate; But to do the best job you can, you just have to be responsible.
I would also like to thank the leaders for encouraging me to try Flutter technology. Understanding and cognition is just the beginning. If there is an opportunity later, you can also try some business. In the industry, Alibaba’s Xianyu is still very in-depth, and meituan’s partners have also made in-depth attempts. Thank them for their contributions to the industry.
Of course, I prefer you to be the front end and have interest in Flutter practice, but you do not have a landing project. You can also contact us and our team can provide you with a shooting range for the landing of business practice. Light talk not practice is false handle, the core or to use business to hit!
Pay attention to our
IMWeb team belongs to Tencent and is one of the most professional front-end teams in China.
We focus on the front-end field for many years, responsible for QQ information, QQ registration, QQ group and other billion business. Currently, it focuses on online education, and carefully hones three major products: Tencent Classroom, Penguin Tutoring and ABCMouse.
Community official website:
imweb.io/
Join us:
Careers.tencent.com/jobdesc.htm…
Scan code to pay attention to IMWeb front-end community public number, get the latest front-end good articles
Micro blog, nuggets, Github, Zhihu can search IMWeb or IMWeb team to follow us.