flutterdartcreateinstance

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]);

Solution

  • 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.