flutter / flutter/flutter

Add a `Container` equivalent that inherits from a theme

Open
#154,740 9 comments 11 reactions 0 assignees View on GitHub
c: new feature f: theming P3 team-design triaged-design
Dominant language
Dart
Stars
179k
Forks
31.1k
PR merge metrics
PR metrics pending

Description

### Use case

I've found Flutter's built-ins (including Material) to be VERY good if you're just trying to get your app built and are not very sensitive to it's specific appearance (ie you don't mind if everything looks like Material). I've found it to be much harder if you're trying to build an app that matches mocks/design guidelines set by a design team.

Consider the following scenario (a real life one I am experiencing at this moment).
I have been tasked with building a Flutter app to match a Figma mock designed according to a proprietary (but very simple) design system:
1. Foreground content are contained in "boxes" (similar to Material's `Card`)
2. Boxes have a `brand_color_a` -> `brand_color_b` vertical gradient fill.
3. Boxes have a `brand_color_c` stroke with a width of `some_int`
4. Boxes have a `some_int` border radius
5. Boxes have a `some_drop_shadow` shadow
6. Boxes pad their child content by `some_edge_insets`

There is no easy way for me to do this with Flutter's built ins today. Lets look at the options:
`Container`
\+ flexible enough to match the design guidelines
\- doesn't inherit from a theme so I'd have to pass the style arguments in everywhere I make a box
`DecoratedBox`
\- same as above, but not as flexible
`Card`
\+ themed
\- isn't flexible enough to add custom drop shadows, gradients, etc
`Mix` (an external [package](url))
\+ flexible
\+ themed
\- not built in
\- weird syntax designed to mimic CSS and thus very non-idiomatic in Flutter
Create my own custom `Box` widget and `BoxTheme` inherited widget
\+ flexible
\+ themed
\- tedious

### Proposal

Create:
1. A `ThemedContainer` widget (name not that important, could be `StyledContainer`, `Box`, etc) with all the same arguments as `Container`
2. A `ContainerTheme` inherited widget and a `ContainerThemeData` data class where the theme data contains all the same styling arguments as `Container` (padding, margin, decoration, color, etc)
3. Add `ContainerThemeData containerTheme` to the core `ThemeData` class

Usage would look something like this:
```dart
// main.dart
return MaterialApp(
theme: ThemeData(
containerTheme: ContainerThemeData(
padding: EdgeInsets.all(someInt),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [brandColorA, brandColorB]),
border: Border.all(width: someWidth, color: brandColorC),
borderRadius: BorderRadius.circular(someInt),
boxShadow: [someBoxShadow],
),
),
),
home: MyApp(),
);

// my_feature.dart
return Column(
children: [
// Acts like a container but the padding, decoration, etc are all set according to the container
// theme defined in main.
ThemedContainer(child: SomeWidgetA()),
// All theme values are inherited except padding which is overridden.
ThemedContainer(child: SomeWidgetB(), padding: EdgeInsets.only(top: 25)),
],
);
```

Open questions:
What happens if the overridden values conflict with the theme values (eg color is overridden to pink, but the theme sets a box decoration and it is not valid to set both `decoration` and `color`)?
I see three okay-but-not-great options here:
A. Throw an assertion error
This is in-line with Container's behavior and fails loudly which is good, but can create a huge mess of confusing errors if you modify theme in a way that introduces conflicts
B. Ignore the entire conflicting set of attributes in the theme and apply the overridden attributes
This silently fails rather than loudly-for example when overriding color one would not expect shadows, etc to also be overridden
C. Remove the potentially conflicting values from `ThemedContainer` (eg the top level `color`)
The conflicting scenario now just isn't possible, but the class is now a little less convenient to use
I prefer A or C.

What happens when `decoration` is set at the widget and theme level and/or at different `ContainerTheme` widgets that have inherit set to true?
We have a pretty similar conflict potential as above. I'd suggest we attempt to merge each nested decoration and throw if there are conflicts. That feels like the most user-expected behavior, but has potential for confusing assertion errors, especially when editing the top level theme.

Should this even got in `MaterialApp`/`Theme`? It's explicitly NOT Material (`Card` is the material solution for this).
`MaterialApp` is in an awkward spot. It's ostensibly tied to Material, but over time it has become far more flexible than Material as developers demand more flexibility out of it. Ideally, Material and Flutter wouldn't have been so deeply intertwined in the first place and Flutter would offer it's own fully-fledged flexible theming system outside of Material. Maybe that ship hasn't sailed yet? You could put this in `WidgetsApp` in it's own theme and then have `MaterialApp` include a top level `containerTheme` argument that just plumbs to `WidgetsApp`, but I'm not sure the added misdirection actually gives us much here-do we want theoretical elegance or practical elegance?
All this said, it wouldn't be a big deal also to just not include the container theme in either place and make devs explicitly include a container theme widget in the widget tree.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.