Is there a way to have an argument with two types in Dart?
Asked Answered
G

4

17

For navigation, I built a simple factory class that generates a ListTile that pushes a route to the Navigator:

static Widget simpleNavRow(String text, BuildContext context, String route) {
  return Column(
    children: <Widget>[
      ListTile(
        title: Text(text),
        onTap: () {
          Navigator.pushNamed(context, route);
        },
      ),
      Divider(),
    ],
  );
}

However, I soon realized that it would be convenient to support pushing widgets as well (or instantiate from their class if possible). I couldn't figure out how to make the "route" argument accept either a String or a Widget, so I created a class that initializes with one of those two types. This code works, but is there a better way to achieve this?

class NavTo {
  String route;
  Widget widget;

  NavTo.route(this.route);
  NavTo.widget(this.widget);

  push(BuildContext context) {
    if (route != null) {
      Navigator.pushNamed(context, route);
    }
    if (widget != null) {
      Navigator.push(context, MaterialPageRoute(builder: (context) {
        return widget;
      }));
    }
  }
}

class ListHelper {
  static final padding = EdgeInsets.all(12.0);

  static Widget simpleNavRow(String text, BuildContext context, NavTo navTo) {
    return Column(
      children: <Widget>[
        ListTile(
          title: Text(text),
          onTap: () {
            navTo.push(context);
          },
        ),
        Divider(),
      ],
    );
  }
}

// usage:
// ListHelper.simpleNavRow('MyWidget', context, NavTo.widget(MyWidget()))
Gauguin answered 10/11, 2018 at 7:18 Comment(2)
what about MaterialApp#onGenerateRoute ? that way you can use pushNamed and still have the freedom for returning any Route you wantJerri
@Jerri In this specific implementation, I am setting up a playground within my project to test out different widgets as I build them. Each component gets its own screen, but it isn't important enough for it to be listed in my routes index. That's why I just want to push a dumb widget.Gauguin
H
18

Since you are expecting one of multiple types, what about having dynamic and then in the push method of NavTo, you could check the type:

class NavTo {
  dynamic route;

  push(BuildContext context) {
    if (route is String) {
       ...
    } else if (route is Widget) {
       ...
    }
  }
}
Hyman answered 10/11, 2018 at 18:53 Comment(3)
To extend my answer, you are looking for a union type. Some research brought me across this solution by Tobe and this article. Hope these help inform how you proceed.Hyman
Thanks. Wasn't sure what it's called. Since it isn't supported by the language, I'll probably just stick to the current solution. A whole library just for this is overkill.Gauguin
dynamic is to general. It would be great if I could say Dog|Cat like in PHP. Nothing to do with OOP by using interface or base class.Mell
R
3

I don’t believe the union type is available in Dart. I like your solution over the use of dynamic as it is strongly typed. You could use named parameters.

NavTo({this.route,this.widget})

But then you don’t have compile-type checking for one and only one parameter.

The only improvement I would make to your constructors is to add @required.

Rajkot answered 10/11, 2018 at 20:19 Comment(0)
H
1

Personnally i like to give Items a MaterialPageRoute params

static Widget simpleNavRow(String text, BuildContext context, MaterialPageRoute route) {
 return Column(
   children: <Widget>[
     ListTile(
      title: Text(text),
      onTap: () {
        Navigator.push(context, route);
      },
     ),
    Divider(),
  ],);
}

items stays dumb like this and i decide what they do in the parent. After you can create an item factory for each type you have that initialize the correct route like this :

class ItemExemple extends StatelessWidget {

final String text;
final MaterialPageRoute route;

ItemExemple(this.text, this.route);

factory ItemExemple.typeA(String text, BuildContext context) =>
  new ItemExemple(text, new MaterialPageRoute(builder: (context) => new   ItemA()));

factory ItemExemple.typeB(String text, BuildContext context) =>
  new ItemExemple(text, new MaterialPageRoute(builder: (context) => new ItemB()));

@override
Widget build(BuildContext context) {
  return Column(
    children: <Widget>[
      ListTile(
        title: Text(this.text),
        onTap: () {
          Navigator.push(context, route);
        },
      ),
      Divider(),
    ],);
}

}
Holcombe answered 11/11, 2018 at 13:58 Comment(0)
L
0

Why are you using one prop for different functions? There is no need to do everything in one function, you are violating the Single Responsibility Rule. You can create two methods for this.

class NavTo {
  String routeName;
  Widget widget;

  NavTo.routeName(this.routeName);
  NavTo.widget(this.widget);

  push(BuildContext context) {
    if (widget != null) {
      Navigator.push(context, MaterialPageRoute(builder: (context) {
        return widget;
      }));
    }
  }

  pushNamed(BuildContext context) {
    if (routeName != null) {
      Navigator.pushNamed(context, route);
    }
  }
}
Leveret answered 12/4, 2023 at 14:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.