Special Delivery Warmer Knowledge Renewal and Discussion of Thoughts With You on Columns

Special Delivery Warmer Knowledge Renewal and Discussion of Thoughts with You on Columns #

Hello, I’m Chen Hang. Since the launch of the column, I have seen many students’ reflections, experiences, and suggestions in the comments section, and of course, there are also many questions.

In order to help everyone better understand the core knowledge points of our column, I have compiled the post-reading questions for each article today, and analyzed and expanded them based on the answers in the comments section.

Of course, I also hope that you can use this Q&A article as a review of the knowledge points covered in the entire column. If you encounter any problems while learning or using Flutter, please continue to leave me a message. Let’s communicate and progress together!

It should be noted that there are no standard answers to these post-reading questions. Even if it’s the same functionality or interface, different people will have completely different implementation solutions. As long as the input and output of your solution meet the requirements of the question, in my opinion, you have already mastered the corresponding knowledge point. Therefore, in this article, I will focus more on introducing solutions, implementation ideas, principles, and key details, rather than specific practical aspects.

Next, let’s take a closer look at the answers to these questions.

Question 1: What are the benefits of building Scaffold page elements directly in the build function using an inline method?

This question is taken from the 5th article “Starting with the Standard Template to Understand How Flutter Code Runs on Native Systems”. You can review the relevant knowledge points in this article first.

Now, let me talk about the biggest advantage of doing this. By building the elements of the page directly in the build function, the page’s state and methods can be directly shared among different components. There is no need for passing state data back and forth between the page and components or using multiple levels of callbacks.

However, there are also drawbacks. Once the data changes, Flutter will rebuild the entire big widget (not just the part that changed), which may have some impact on performance.

Question 2: How can we make the elements inside collection types like List and Map support multiple types?

This question is from the 6th article “Basic Syntax and Type Variables: How Does Dart Represent Information?”. You can review the relevant knowledge points in this article first.

If multiple types in the collection have a common parent class (such as double and int), you can use the parent class for container type declaration to increase type safety. When retrieving objects from the collection, you can convert them to the actual type based on runtimeType. If there are many types in the container and you want to eliminate the need for type conversion, you can also use the dynamic type to add elements of different types.

To determine the real type of an element, you can use the is keyword or runtimeType.

Question 3: Related questions about inheritance, interfaces, and mixins.

This question is from the 7th article “Functions, Classes, and Operators: How Does Dart Process Information?”. You can review the relevant knowledge points in this article first.

Firstly, how do you understand inheritance, interface implementation, and mixins? When should we use them?

Inheritance, interface implementation, and mixins are all means to achieve code reuse. We should use them in our code based on different requirements. Here are the scenarios for using each of them:

  • Inheritance: Subclasses reuse the implementation of the superclass. This is suitable for scenarios where there is a logical hierarchy between the two classes.
  • Interface implementation: A class reuses the parameters, return values, and method names of an interface but not its method implementation. This is suitable for scenarios where there is a logical hierarchy between the interface and class in terms of behavior.
  • Mixins: A class can reuse the implementation of multiple classes without requiring a parent-child relationship between them. This is suitable for scenarios where there is a partial logical hierarchy between multiple classes.

Secondly, in the scenario of inheritance, what is the order of execution of constructors between the superclass and subclass? If both the superclass and subclass have multiple constructors, how to ensure the correct invocation of constructors between them at the code level?

By default, the subclass’s constructor will automatically call the superclass’s default constructor. If you need to explicitly call the superclass’s constructor, you need to call it at the beginning of the body of the subclass’s constructor. However, if the subclass provides an initialization parameter list, it will execute before the superclass’s constructor.

Between constructors, there may be some common logic. If you write this logic in each constructor separately, it may be cumbersome. Therefore, constructors can be passed, which means filling in the parameters of another constructor to initialize the class. Therefore, the passable constructor has no method body and it is only called in the initialization list.

If both the superclass and subclass have multiple constructors, it is usually for simplifying the initialization code of the class and setting some properties to default values. In this case, as long as we can ensure that each constructor has a clear initialization path and no properties are missed, it’s fine. A good practice is to forward constructors with fewer parameters to the constructors with more parameters. The constructor with the most parameters should call the superclass’s constructor with the most parameters.

Question 4: Extend the shopping cart example program to support the “quantity” attribute of items and display information about the item list (including item name, quantity, and unit price).

This question is from the 8th article “Comprehensive Case: Mastering Dart’s Core Features”. You can review the relevant knowledge points in this article first.

To implement this extension, as I mentioned earlier, everyone may have completely different solutions. Here, I will give you a hint. You can add a “quantity” attribute in the Item class and iterate over the product information in the shopping cart when printing the receipt.

It is important to note that when adding the “quantity” attribute, the count should be set to 1 when calculating the price for merged items, rather than being accumulated. For example, merging five pounds of apples with three boxes of chocolates should result in one package of apple-chocolate combo, not eight packages of apple-chocolate combo.

Question 5: What is the relationship between Widget, Element, and RenderObject? Can you find the corresponding concepts in Android/iOS/Web? #

This question is from the 9th article “Widget, the cornerstone of building Flutter interface”.

Widget is for data configuration, RenderObject is responsible for rendering, and Element is an intermediate class between them, used for rendering resource reuse.

Widget and Element are one-to-one correspondence, but RenderObject is not. Only the widgets that actually need layout and drawing will have RenderObject.

In Android/iOS/Web development, the corresponding concepts are:

  • In iOS, Xib is equivalent to Widget, UIView is equivalent to Element, CALayer is equivalent to RenderObject;
  • In Android, XML is equivalent to Widget, View is equivalent to Element, Canvas is equivalent to RenderObject;
  • In Web, taking Vue as an example, Vue template is equivalent to Widget, virtual DOM is equivalent to Element, DOM is equivalent to RenderObject.

Question 6: What is the difference between the State constructor and initState? #

This question is from the 11th article “What are we talking about when we talk about lifecycle?”.

When the State constructor is called, the Widget has not completed initialization, so it is only suitable for some UI-independent data initialization, such as processing the parameters passed from the parent class.

When the initState function is called, the StatefulWidget has already completed the insertion of the Widget tree, so some initialization work related to the Widget, such as setting a scroll listener, must be placed in initState.

Question 7: What are the actual visual components that carry the visual functions of Text, Image, and button widgets? #

This question is from the 12th article “Classic Widgets (1): How to use text, images, and buttons in Flutter?”.

Text is a StatelessWidget that encapsulates RichText. Image is a StatefulWidget that encapsulates RawImage. Buttons, on the other hand, are StatefulWidgets that encapsulate RawMaterialButton.

As you can see, StatelessWidget and StatefulWidget only encapsulate the containers of widgets and do not participate in actual drawing. The visual components that are responsible for rendering are the ones that inherit from RenderObject, such as RichText and RawImage.

Question 8: How to pre-cache children elements in ListView? #

This question is from the 13th article “Classic Widgets (2): What are UITableView/ListView in Flutter?”.

In the ListView constructor, there is a cacheExtent parameter, which is the length of the pre-rendering area. ListView will reserve an area of cacheExtent length on both sides of its visible area as a pre-rendering area, which is equivalent to pre-caching some elements, so that when scrolling, they can be displayed quickly.

Question 9: How are the sizes of Row and Column determined? What happens when they are nested? #

This question is from the 14th article “Classic Layout: How to define the layout of child widgets in a parent container?”.

The sizes of Row and Column are determined by the size of the parent widget, the size of the child widgets, and mainAxisSize.

Row and Column will only occupy as much space as possible in the main axis direction (max: the size of the screen direction main axis or the size of the parent widget in the main axis direction; min: the size of all child widgets combined in the main axis direction), while the length in the cross axis is determined by the length of their largest child element.

If a Row is nested inside another Row, or a Column is nested inside another Column, only the outermost Row or Column will occupy as much space as possible. The inner Row or Column will occupy its actual size.

Question 10: Add night mode switching functionality to the UpdatedItem widget. #

This question is from the 16th article “Starting from night mode, how to customize different styles of the app theme?”.

This is a practical question. Similarly, I will only give you a hint about the implementation approach: You can use a variable in ThemeData to determine which theme is currently used. Then, drive the variable update in State.

Question 11: How to handle resource images based on pixel density for devices with densities of 3.0 and 1.0? #

This question is from the 17th article “Dependency Management (1): How to use images, configurations, and fonts in Flutter?”. The principle of device adaptation based on resource image pixels is: adjust to use the most appropriate resolution resource, that is, a device with a pixel density of 3.0 will choose a 2.0 rather than a 1.0 resource image; and for devices with a pixel density of 1.0, compression will be performed on resource images with a pixel density greater than 1.0.

Question 12: Should .packages and pubspec.lock be version controlled?

This question comes from the 18th article “Dependency Management (2): How to manage third-party component libraries in Flutter?”.

pubspec.lock needs to be version controlled because the lock file records the explicit and implicit dependency relationships of the current project in Dart when computing project dependencies. We can directly use this result to unify the project development environment.

.packages does not need to be version controlled because this file records the local cache files of all dependencies of the current project when computing project dependencies in Dart. It is related to the local environment and does not need to be unified.

Question 13: How does GestureDetector respond to events when embedded with FlatButton?

This question comes from the 19th article “How to respond to user interaction events?”.

In a UI where a parent container contains a FlatButton, if the parent container uses GestureDetector to listen to the onTap event, clicking the button will not trigger a response from the parent widget. This is because the Gesture Arena will only respond to one (child widget) at a time.

If the onTap event is listened to, after double-clicking the button, the parent container’s double-tap event will be recognized. This is because the child widget does not handle the double-tap event and does not need to go through the Gesture Arena’s competition process.

Question 14: Please summarize the characteristics of property passing, InheritedWidget, Notification, and EventBus.

This question comes from the 20th article “When it comes to cross-component data transmission, you only need to remember these three tricks”.

Property passing is suitable for use within the same view tree, with the direction of passing values from parent to child, using the constructor to pass values as properties, which is simple and efficient. The disadvantage is that when it involves cross-layer transmission, the property may need to pass through many layers to reach the child component, causing many intermediate components that do not need this property to receive data from their child widgets, which is cumbersome and redundant.

InheritedWidget is suitable for scenarios where a child widget actively retrieves data from an upper layer, with the direction of passing values from parent to child, and can achieve cross-layer data reading sharing. InheritedWidget can also achieve write sharing by encapsulating methods for writing data at the upper layer for the lower layer to call. The advantage is that data transmission is convenient, achieving the decoupling of logic and view without code intrusion; the disadvantage is that if the hierarchy is deep, a large refresh scope will affect performance.

Notification is suitable for scenarios where a child widget pushes data to the parent widget, with the direction of passing values from child to parent, and can achieve cross-layer data change sharing. The advantage is that the same event of multiple child elements can be handled uniformly by the parent element, which is simple for many-to-one scenarios; the disadvantage is that the process of customizing Notification is somewhat cumbersome.

EventBus is suitable for communication between entities that do not need to have a parent-child relationship, and subscribers need to explicitly subscribe and unsubscribe. The advantage is that it supports the passing of any object and is simple to implement with a one-to-many approach; the disadvantage is that the subscription management is somewhat cumbersome.

Question 15: Implement a calculation page that can calculate the sum of the 2 numerical parameters passed in from the previous page and inform the previous page of the calculation result when the page is closed.

This question comes from the 21st article “Routing and Navigation: How Flutter implements page switching”.

This is a practical question that requires you to implement it yourself. Here, I will give you a hint: basic routes can pass parameters through constructor properties or by adding parameters settings in MaterialPageRoute to pass page parameters.

When opening the page, we can use the above mechanism to pass the parameters (2 numbers) to the basic route and register the then callback to listen for the close event of the page; when the page needs to be closed, we retrieve the 2 number parameters, calculate the sum, and call the pop function.

Question 16: In AnimatedBuilder, what are the roles of the outer child parameter and the inner child parameter in the builder function?

This question comes from the 22nd article “How to create cool animation effects?”.

The outer child parameter defines the rendering, while the inner child parameter in the builder defines the animation, separating the animation and rendering. Through the builder function, the range of rebuilding is limited, so there is no need to rebuild the entire widget every time during the animation.

Question 17: In the example of concurrent Isolate computing factorial, what is the reason for having two SendPorts for the concurrent Isolate?

// Concurrent computation of factorial
Future<dynamic> asyncFactorial(n) async {
  final response = ReceivePort(); // Create a pipe
  // Create concurrent Isolate and pass the pipe
  await Isolate.spawn(_isolate, response.sendPort);
  // Wait for Isolate to return the pipe
  final sendPort = await response.first as SendPort;
  // Create another pipe named answer
  final answer = ReceivePort();
  // Send the parameter to the Isolate pipe and pass the answer pipe
  sendPort.send([n,answer.sendPort]);
  return answer.first; // Wait for Isolate to return the result through the answer pipe
}

// Isolate function, with the pipe passed from the main isolate as a parameter
_isolate(initialReplyTo) async {
  final port = ReceivePort(); // Create a pipe
  initialReplyTo.send(port.sendPort); // Send the pipe back to the main isolate
  final message = await port.first as List; // Wait for the main isolate to send a message (parameter and return result pipe)
  final data = message[0] as int; // Parameter
  final send = message[1] as SendPort; // Return result pipe
  send.send(syncFactorial(data)); // Call the synchronous factorial calculation function and return the result
}

// Synchronous calculation of factorial
int syncFactorial(n) => n < 2 ? n : n * syncFactorial(n-1);
main() async => print(await asyncFactorial(4)); // Wait for the result of concurrent factorial computation

This question is from the 23rd article “How does the single-threaded model ensure smooth UI operation?”.

SendPort/ReceivePort is a unidirectional pipe that helps us achieve the concurrent Isolate returning the execution result to the main Isolate: the concurrent Isolate is responsible for using the SendPort for sending, while the main Isolate is responsible for using the ReceivePort for receiving. In the process of returning the execution result, the main Isolate has no way to actively do anything other than waiting.

In this example, the concurrent Isolate sent data twice with SendPort, which means that the main Isolate also needs to use ReceivePort to wait twice. If the concurrent Isolate sends data three times with SendPort, the main Isolate also needs to wait three times with ReceivePort.

So a better approach is to use SendPort/ReceivePort only once, and once sending/receiving is completed, they are no longer used. But what if we need to send/receive again next time?

At this time, we can refer to the approach used in this factorial calculation example, and pass the SendPort needed for the next time as a parameter when sending data.

Question 18: Custom dio interceptor to check and refresh token.

This question is from the 24th article “HTTP Network Programming and JSON Parsing”.

This is also a practical question, and I will only provide you with the key idea: in the onRequest method of the interceptor, check whether the token exists in the header. If it does not exist, send a new request to obtain the token and update the header. Considering that multiple requests may be sent simultaneously, and the token may be requested multiple times, we can lock/unlock the interceptor by calling the lock/unlock methods of the interceptor.

Once the request/response interceptor is locked, the subsequent requests/responses will be queued and wait before entering the interceptor until it is unlocked. Then these enqueued requests will continue to execute (enter the interceptor).

Question 19: Issues related to persistent storage.

This question is from the 25th article “Using and optimizing local storage and databases”.

First, let’s take a look at the scenarios where files, SharedPreferences, and databases are suitable for persistent data storage.

  • Files are suitable for persisting large amounts of ordered data.
  • SharedPreferences is suitable for caching a small amount of key-value pairs.
  • Databases are used to store large amounts of formatted data that require frequent updates.

Next, let’s take a look at how to perform database cross-version upgrades.

Database upgrades essentially involve modifying table structures. If the upgrade process is continuous, we only need to execute table structure modification statements for each version. However, if the upgrade process is not continuous, such as directly upgrading from version 1.0 to 5.0, skipping versions 2.0, 3.0, and 4.0:

1.0->2.0: Execute table structure modification statement A
2.0->3.0: Execute table structure modification statement B
3.0->4.0: Execute table structure modification statement C
4.0->5.0: Execute table structure modification statement D

Therefore, during the database migration to 5.0, we cannot only consider the table structure of 5.0 and execute the upgrade logic for D. We also need to consider the table structures from 2.0, 3.0, and 4.0, and execute all the upgrade logic between 1.0 and 4.0:

switch(oldVersion) {
case '1.0': do A;
case '2.0': do B;
case '3.0': do C;
case '4.0': do D;
default: print('done');
}

This way, everything will be fine.

However, it’s worth noting that in the switch statement in Dart, the break statement for conditional statements cannot be omitted. You can think about how to write a C++-like fallthrough switch in Dart.

Question 20: Extend the implementation of openAppMarket to allow us to navigate to the app market of any app.

This question is from the 26th article “How to Provide Platform-Specific Implementations in the Dart Layer? (Part 1)”.

For this question, my hint for you is: When Dart calls the invokeMethod method, you can pass in a Map type key-value pairs parameter (containing the bundle ID for iOS and package name for Android), and then retrieve the parameter in the native code host.

Question 21: Extend the implementation of embedding native views to achieve the requirement of dynamically changing the color of the native view.

This question is from the 27th article “How to Provide Platform-Specific Implementations in the Dart Layer? (Part 2)”.

For this question, my hint is similar to the previous one: When Dart calls the invokeMethod method, you can pass in a Map type key-value pairs parameter (containing the RGB information of the color), and then retrieve the parameter in the native code host.

Question 22: Are there any differences in the packaging and extraction process for Flutter module projects with resource dependencies and extracting component libraries?

This question is from the 28th article “How to Integrate Flutter Projects into Native Apps?”.

The answer is not much different. Because the Flutter module files themselves contain resource files.

If the module project has dependencies on native plugins, the extraction process also requires the .flutter-plugins file, which records the cache address of the native dependencies of the plugin, to encapsulate the native part of the component dependencies. For specific details, you can refer to the 44th article.

Question 23: How can we ensure that the transition animations between the two types of pages in a hybrid project have consistent overall effects?

This question is from the 29th article “Managing Navigation Stack in Hybrid Development”.

First, these two types of page transition animations are: transition animations between native pages and transition animations between Flutter pages. To ensure consistent overall effect, there are two options:

  • The first option is to customize the switch animations for the native project (mainly for Android) and the switch animations for Flutter separately.
  • The second option is to use a mechanism similar to the shared FlutterView used by Xianyu, where the page switching is uniformly handed over to the native code, and the FlutterView is only responsible for refreshing the interface.

Question 24: How to use Provider to share two objects of the same type?

This question is from the 30th article “Why do we need state management and how do we do it?

The answer is simple. You can encapsulate a big object and encapsulate the two objects of the same type as its internal properties.

Question 25: How to make Flutter code receive push messages faster?

This question is from the 31st article “How to implement native push capability?”.

We need to first determine whether the application is in the foreground or background, and then handle it accordingly:

  • If the application is in the foreground and has completed initialization, the native code directly calls the method channel to notify Flutter; if the application has not completed initialization, the native code stores the message locally and waits for the Flutter application to initialize before actively pulling.
  • If the application is in the background, the native code stores the message locally, wakes up the Flutter application, and waits for the Flutter application to initialize before actively pulling through the method channel.

Question 26: How to internationalize image resources?

This question is from the 32nd article “In addition to multilingual, what else do we need to pay attention to when adapting internationalization?”.

In fact, internationalizing image resources is fundamentally no different from text resources. You just need to declare different images in the arb file separately. For specific implementation details, you can review the relevant content of this article again.

Question 27: How to implement landscape and portrait screen switching for adjacent pages?

This question is from the 33rd article “How to adapt to different resolutions of mobile screens?”.

This implementation method is very simple. You can set the screen orientation in the initState method and restore the screen orientation in the dispose method.

Question 28: How to support switching between different configurations while keeping the production environment code unchanged?

This question is from the 34th article “How to understand Flutter’s compilation mode?”.

Similar to configuring night mode, we can use a status switch to determine which configuration is currently in use, set the entry point, and then drive the variable update in the State. As for the configuration of night mode, you can review the relevant content in the 16th article “Starting from night mode, how to customize the theme of the app with different styles?”.

Question 29: Change debugPrint to cycle log writing.

This question is from the 36th article “How to optimize the development and debugging efficiency through toolchain?

Regarding this question, the hint I give you is to define different main files to define the debugPrint behavior: define main-dev.dart as outputting logs to the console, and define main.dart as outputting to a file. The current operating file name is set to 0 by default, and after it is filled, the file name increases by 5 mod sequentially and is synchronized to SharedPreferences, and the file is cleared and rewritten.

Question 30: Use concurrent Isolate to perform MD5 calculation.

This question is from the 37th article “How to detect and optimize the overall performance of a Flutter App?”.

Regarding this question, the hint I give you is to transform the interface into a StatefulWidget, start the calculation of MD5 in the initialization of StatefulWidget, and use compute to start the calculation. In the build function, determine whether the MD5 data exists. If not, show CircularProgressIndicator; if it exists, show ListView.

Question 31: How to add unit test cases for SharedPreferences using mockito?

Future<bool> updateSP(SharedPreferences prefs, int counter) async {
bool result = await prefs.setInt('counter', counter);
return result;
}

Future<int> increaseSPCounter(SharedPreferences prefs) async {
    int counter = (prefs.getInt('counter') ?? 0) + 1;
    await updateSP(prefs, counter);
    return counter;
}

This question comes from the 38th article “How to Improve Delivery Quality Through Automated Testing?”.

The updateSP and increaseSPCounter functions depend on the setInt and getInt methods of SharedPreferences, where the former is an asynchronous function and the latter is a synchronous function.

Therefore, we only need to simulate the corresponding data return for setInt and getInt. For setInt, we only need to return true when the parameter is 1:

when(prefs.setInt('counter', 1)).thenAnswer((_) async => true);

For getInt, we only need to return 2:

when(prefs.getInt('counter')).thenAnswer((_) => 2);

Other parts are no different from regular unit testing.

Question 32: How to collect exceptions from concurrent Isolates?

This question comes from the 39th article “What to Do with Exception Capture and Information Gathering When Problems Occur Online?”.

Exceptions in concurrent Isolates cannot be caught with try-catch. Concurrent Isolates communicate with the main Isolate using the message mechanism of SendPort, and exceptions can be considered as a form of message passing. Therefore, if the main Isolate wants to catch exceptions from concurrent Isolates, it can pass a SendPort to the concurrent Isolate.

The spawn function, which creates Isolates, has an onError parameter of type SendPort. Therefore, the concurrent Isolate can send messages to this parameter to notify exceptions.

Question 33: How to measure the loading time of a page that depends on one or more network API data?

This question comes from the 40th article “Three Metrics We Need to Focus on When Evaluating the Quality of a Flutter App Online”.

The loading time of a page is the difference between the time the page finishes rendering and the time it initializes. So, we just need to record the page initialization time when entering the page, refresh the interface and start a one-time frame callback to detect when the page finishes rendering, and then calculate the difference between the two times. The same applies if the rendering of the page involves multiple APIs.

Question 34: How to set the Flutter version in Travis?

This question comes from the 42nd article “How to Build an Efficient Environment for Flutter App Packaging and Deployment?”.

The setting is very simple. When cloning the Flutter SDK in the before_install field, simply specify the specific branch:

git clone -b 'v1.5.4-hotfix.2' --depth 1 https://github.com/flutter/flutter.git

Question 35: How to quickly implement the standardization of plugin definition using reflection?

This question comes from the 44th article “How to Build Your Flutter Hybrid Development Framework - Part 2?”.

In Dart, when calling a non-existing or unimplemented interface, it can be uniformly handled using the noSuchMethod method. This method carries an Invocation parameter, which provides the called function name and arguments:

String methodName = invocation.memberName.toString().substring(8, string.length - 2);
dynamic args = invocation.positionalArguments;

Among them, the args parameter is a List variable, and we can parse the relevant parameters one by one in the native code host. With the function name and arguments, we can dynamically call native methods using reflection on the plugin class instance.

Compared with traditional method calls, performing method calls using reflection involves more steps. We need to find and initialize the instances of classes involved in the reflection call process, find the method object, create the parameter list object, and then perform reflection calls. We can follow the example to do these steps.

Calling on the Android side:

public void onMethodCall(MethodCall call, Result result) {
    ...
    String method = call.argument("method"); // Get the function name
    ArrayList params = call.argument("params"); // Get the parameter list
    Class<?> c = FlutterPluginDemoPlugin.class; // Obtain the reflection application object
    Method m = c.getMethod(method, ArrayList.class); // Get the method object
    Object ret = m.invoke(this, params); // Call the reflection method on the plugin instance and get the return value
    result.success(ret); // Return the execution result
    ...
}

Calling on the iOS side:

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    ...
    NSArray *arguments = call.arguments[@"params"]; // Get the function name
    NSString *methodName = call.arguments[@"method"]; // Get the parameter list
    SEL selector = NSSelectorFromString([NSString stringWithFormat:@"%@:",methodName]); // Get the Slector corresponding to the function
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector]; // Get the method signature on the plugin instance
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; // Generate a reflection invocation object based on the method signature
            
    invocation.target = self; // Set the execution object of the invocation
    invocation.selector = selector; // Set the selector of the invocation
    [invocation setArgument:&arguments atIndex:2]; // Set the invocation argument
    
    [invocation invoke]; // Perform reflection
    
    NSObject *ret = nil;
    if (signature.methodReturnLength) {
        void *returnValue = nil;
        [invocation getReturnValue:&returnValue];
        ret = (__bridge NSObject *)returnValue; // Get the reflection call result
    }    
                  
    result(ret); // Return the execution result
    ...      
}

These are the answers to all the discussion questions in the “Flutter Core Technologies and Practices” column. If you have any other questions, feel free to leave a message and we can discuss together.