I have a set of around 8 widgets that all accept a single parameter of type X and display the content of type X in a different way. What I am trying to create is an abstraction layer that defines the structure of such a widget. Besides the structure, the abstraction layer would define a factory method to decide which implementation gets used based on an ID. The different implementations are all widgets that extend either Stateless- or StatefulWidget.

The abstraction layer would look like the following:

abstract class AbstractWidget {
final X content;

factory AbstractWidget({@required int id, @required X content}) {
  switch (id) {
    case 1:
      return Implementation1(content);
      break;
    default: return Implementation2(content);
   }
  }
 }

An implementation would look like the following:

class Implementation1 extends StatelessWidget implements AbstractWidget {
  final X content;

  Implementation1(this.content);

  @override
  Widget build(BuildContext context) {
    // Display content in some type of way
  }
}

So what I'm trying to achieve is the following:

var widgetList = new List<Widget>();
for (var item in items) {
  X content = fetchContentFromAPI();
  widgetList.add(AbstractWidget(content: content, id: item.id));
}
return Column(children: widgetList);

This would not work because the AbstractWidget isn't technically a Widget-type, even though it can only return instances of either Stateless- or StatefulWidgets. If anyone knows a better way of implementing my structure, it would help me a lot!

1 Answers

0
Hugo Passos On Best Solutions

Make your AbstractWidget extend or implement Widget. However, I must agree with RĂ©mi Rousselet. An abstract class should not know anything about its children (that's why it is an abstraction, anyway). I would do, instead:

class Foo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var widgetList = new List<Widget>();
    for (var item in items) {
      X content = fetchContentFromAPI();
      widgetList.add(abstractWidgetWith(content: content, id: item.id));
    }
    return Column(children: widgetList);
  }

  Widget abstractWidgetWith({@required int id, @required X content}) {
    switch (id) {
      case 1:
        return Implementation1(content);
      default:
        return Implementation2(content);
    }
  }
}

abstract class AbstractWidget {
  final X content;
  AbstractWidget(this.content);
}

class Implementation1 extends StatelessWidget implements AbstractWidget {
  final X content;

  Implementation1(this.content);

  @override
  Widget build(BuildContext context) {
    // Display content in some type of way
  }
}

class Implementation2 extends StatelessWidget implements AbstractWidget {
  final X content;

  Implementation2(this.content);

  @override
  Widget build(BuildContext context) {
    // Display content in some type of way
  }
}

I just want to make an addition about something you said:

It's just that the extensive switch case goes against the design patterns and principles I've been taught.

The idea here is to always look for an abstraction instead of repeating condition structures. Notice repeating emphasizing. If the condition structure is used more than once, abstraction is mostly a better option. If this is not the case, you are probably overkilling the problem by creating abstraction.

Again, notice repeating emphasizing. You tend to need an abstraction when you have lots of condition structures, but I'll end up using ONE condition in the end. In other words, you can't get rid of the condition structures, you are just going to use it less.

In the context of this question, seems like you followed all the rules. This looks like a clean code to me.