简体   繁体   中英

Dynamically create instance from type name

There's the question – could I create class instance if I have string variable which contains its name?

For example, I have

var className = 'DocumentsList';

could I do something like this

var docListWidget = createInstance(className[, params]);

Flutter (and Dart) do not have the facilities to do that. On purpose. They implement tree shaking, a mechanism to remove unused code to make your app smaller and faster. However, to do that, the compiler has to know what code is used and what is not. And it cannot possibly know what code gets used if you can do stuff like you describe.

So no, this is not possible. Not with that degree of freedom. You can have a big switch statement to create your classes based on strings, if you know in advance which string it will be. That is static, the compiler can work with that.

What you want is called "reflection" and you can add some capabilities using packges like reflectable or mirror but they cannot change the compilation process, they too work through the fact that you specify beforehand which classes need reflection. A totally dynamic usage is just not possible (on purpose).


Since you mentioned the routing table: You cannot create a class from a string, you can however create the string form the class:

import 'package:flutter/material.dart';

typedef Builder<T> = T Function(BuildContext context);

String routeName<T extends Widget>() {
  return T.toString().toLowerCase();
}

MapEntry<String, Builder<Widget>> createRouteWithName<T extends Widget>(Builder<T> builder) {
    return new MapEntry(routeName<T>(), (context) => builder(context));
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: routeName<ScreenPicker>(),
      routes: Map.fromEntries([
        createRouteWithName((context) => ScreenPicker()),
        createRouteWithName((context) => ScreenOne()),
        createRouteWithName((context) => ScreenTwo()),
      ]),
    );
  }
}

class ScreenPicker extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: Text("Navigate a route")), 
                    body: Column(children: [
                      RaisedButton(
                        child: Text('One'),
                        onPressed: () => Navigator.pushNamed(context, routeName<ScreenOne>())),
                      RaisedButton(
                        child: Text('Two'),
                        onPressed: () => Navigator.pushNamed(context, routeName<ScreenTwo>())),
                    ]));
  }
}

class ScreenTwo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: Text("Second Screen")), body: Center(child: Text("Two")));
  }
}

class ScreenOne extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: Text("First Screen")), body: Center(child: Text("One")));
  }
}

This way you have no strings with route names in your project that can be changed or mistyped or forgotten when renaming something.

The best way to do that is using a named constructor like:

class MyClass {
    const MyClass.create();
}

So the way you do this is passing the Type calling the method within. Remember you can pass the function without calling it, like:

Get.lazyPut(MyClass.create);

If you really need to use String for identifying the class type I suggest you create some mapping for it.

There's some functions from rootBundle that can be useful in this situation, like rootBundle.loadString() that can be done to load code from files.

Hope this was helpful.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM