iTranslated by AI
Tips for Creating a Polished UI in Flutter
Are you Fluttering?
Hi, I'm Sugit.
Previously, I wrote an article overflowing with love for Flutter.
I intended to keep writing Flutter articles one after another, but I haven't been able to find the time.
After talking so passionately about your love for Flutter, maybe you didn't actually love it that much?
Were you cheating on it with another platform?
So much time has passed that it wouldn't be surprising if people thought that.
Actually, my love for Flutter overflowed so much that I couldn't help but want to make an app, and I was absorbed in app production. Here it is.
"Yaruhyaku" is an app version of the "Want-to-do List 100" that I write in my notebook every year.
Until now, when I made a want-to-do list, it always ended up feeling like task management, and checking whether I'd actually done them was left untouched until the end of the year.
However, I suddenly thought that I should cherish the things I've set for myself more. This led me to start creating an app where you can record your want-to-do list not just as tasks, but as precious memories.
I posted the development process on Twitter every day, so it's a thread. Please take a look if you'd like. I finished it in about 40 days. At about 2 hours a day, that's 80 hours (๑>ᴗ<) All thanks to Flutter.
Now, first of all, starting from here...
How should I put it, it feels very "Fluttery"
This feeling emerged as soon as I started developing the app with Flutter. It's not necessarily a bad thing, but it ended up looking like something where anyone who knows would immediately think, "This is Flutter, right?"
This want-to-do list management app, "Yaruhyaku," was made with Flutter but is currently iOS-only. This is simply because I don't own an Android device and can't test it sufficiently. Also, since I'm doing solo development while balancing childcare and my main job, the time I could dedicate was limited.
Now, regarding the Flutter UI, I think there are two main choices:
- Material
- Cupertino
Since I wanted to release "Yaruhyaku" on Android in the future, I chose Material without hesitation. I heard there are techniques to switch the UI according to the platform, but that was still a bit too advanced for me, so I excluded it from my options.
Regarding this, mono-san summarized it on Twitter previously, so it would be good to follow the thread from here.
So, having chosen Material, I busily implemented the app image I had sketched on my iPad. Once the rough mock-up was ready, I installed it on my iPhone, and I suddenly noticed something.
"The atmosphere is different from other apps."
When I actually install it on an iPhone, I inevitably compare it with other installed apps. I'm not sure if this description is correct, but since many other apps are built natively, I felt, "This feels very iPhone-like."
Is the Material design not fitting for the iPhone?
I started having that doubt.
I love Material Design. Even when I make apps with React, I use Material-UI. However, I became concerned that using Material on iOS inevitably makes the atmosphere different from native iOS apps.
But I love Flutter.
Flutter is not to blame, Material is not to blame, my way of using it is the problem.
So, I closely compared my mock-up app with the apps actually installed on my iPhone and went through trial and error to see how I could achieve a good UI.
By the way, I am not a designer.
I'm just a science-track guy.
I will now explain the conclusion I reached (though it might not be that big of a deal) after all that trial and error. (My apologies for the very long introduction.)
Let's Get Polished
I will introduce several points that I noticed.
Part 1: Handle Dark Mode Support First
It might feel like I'm suddenly forcing dark mode support on you, but I'll explain the reason now. As far as I've checked, most major apps installed on the iPhone support dark mode. My app didn't support it at the mock-up stage.
Then I realized: when the device is set to dark mode, a white-based app is quite a shock to the eyes.
In other words, if you don't support dark mode, there's a possibility of startling the user. In this day and age where dark mode has become so widespread, it might be unavoidable. So, it's not me forcing it; it's society. (Exaggeration)
Now, when you try to implement dark mode, you inevitably have to consider which colors change between dark mode and the regular mode. Furthermore, the colors that switch according to the mode must strictly be colors with a "light feel" and a "dark feel."
The little devil 😈 in my heart whispers:
"It's impossible for a design amateur to handle difficult concepts like 'light feel' and 'dark feel.' At least, it's impossible for you."
Then, the little angel 🧚 in my heart whispers:
"It's okay. For a light feel, white and light gray are fine, and for a dark feel, dark gray is enough. Flutter has themes prepared that look just like that."
LOVE Flutter
They've prepared great themes so that amateurs don't have to worry about colors! Isn't that the best?
Using a good theme is very easy. It looks like this:
- Immediately after
flutter create
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
- Great dark mode support
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
Fabulous...
It's great. Dark mode support is already done.
Furthermore, for those who want to make fine adjustments, the copyWith() method is available. This is a method to achieve the goal of "I want to customize just this part" while using the light or dark themes provided by Flutter.
Let's try changing the primaryColor as an experiment.
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.light().copyWith(
primaryColor: Colors.green,
),
darkTheme: ThemeData.dark().copyWith(
primaryColor: Colors.green,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
Then, it looks like this!
Jesus
Oh my goodness, even though Flutter had made it beautiful, I've easily completed a tacky color scheme. Main black with bright green and fluorescent accents—it's like a new species of drone beetle discovered in the jungle.
We've gained the next lesson here. (Keeping my cool)
Part 2: Use Colors Other Than White and Black Only Where Necessary
Let's take a look at several iOS apps side-by-side.
From the left:
- Google News
- Medium
They all exist in a completely white world. Medium uses a gentle green, and the other three have slightly light blue as accents, but otherwise, photos and avatars are the main focus. Of course, all these apps also support dark mode.
The reason Twitter looks a bit dark blue is because of its settings; the standard dark mode was also fine.
What we can learn from this is that for non-designers like myself, following this "big wave" (trend) is a shortcut to achieving a polished look.
In other words, it means finishing everything except for the "accents" in white and black tones.
And then I came across the next realization.
Part 3: Colored AppBars Might Have Been Too Early for Non-Designers
The AppBar is something that comes by default in Flutter and just sort of exists there, but are you making the most of it?
An AppBar allows you to place buttons on the left and right, and a slightly larger piece of content can be placed in the center. The default is a title (Text widget).
Take a look at the AppBars of those "big wave" apps from earlier.
Aren't they white-toned in normal mode and black-toned in dark mode?
That's just how iOS is.
At this point, if you are familiar with Flutter or iOS development, you might think, "Well, that's common sense... that's how iOS is. Cupertino widgets are the same way..."
Yes, exactly. If I had used Cupertino widgets, I wouldn't have had any doubts. But Flutter beginners start with Material. Even if they want to make an iOS app, they start with Material. (Is it just me...?)
For those who want to try building a Flutter app using Cupertino widgets, I recommend this Google Codelab: https://codelabs.developers.google.com/codelabs/flutter-cupertino#0
Anyway, while using Cupertino widgets seems like it would solve things easily, we're going with Material this time. Let's try making the AppBar white-toned.
If I try it like this...
theme: ThemeData.light().copyWith(
primaryColor: Colors.white,
),
It looks like this.
Jesus again
It doesn't go quite as planned.
Then, I thought, "If the AppBar text isn't visible, shouldn't I just change that color to black?" and acting on impulse again...
appBar: AppBar(
title: Text(widget.title, style: TextStyle(color: Colors.black)),
),
Now it's invisible in dark mode. Also, in normal mode, while the title is visible, the iOS system-specific parts (like the battery icon) are white and cannot be seen.
This is how you get stuck.
In fact, if you only change the primaryColor in the AppBar, the iOS system-specific color mode doesn't follow along. So, it assumes the default bluish AppBar of ThemeData.light() and stays white.
So, what is the proper way to change the AppBar color? To be honest, I'm not sure which way is best. However, while ThemeData.light().copyWith() is somewhat convenient when the AppBar has color, it requires a lot of adjustments when it's white, so I think it's better to take a different approach.
That approach is primarySwatch.
For example, if you do this:
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.lime,
),
darkTheme: ThemeData.dark(),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
It looks like this.
This works fine for colored themes. Now let's try setting it to White.
When I do that, I get an error.
flutter: type 'Color' is not a subtype of type 'MaterialColor'
Colors.white is not a MaterialColor, so it cannot be assigned to the Swatch.
I was at a loss.
But I found an issue on GitHub. It seems it can be handled with brute force 🏋️. Muscles are important for programming.
class MyApp extends StatelessWidget {
final MaterialColor materialWhite = const MaterialColor(
0xFFFFFFFF,
const <int, Color>{
50: const Color(0xFFFFFFFF),
100: const Color(0xFFFFFFFF),
200: const Color(0xFFFFFFFF),
300: const Color(0xFFFFFFFF),
400: const Color(0xFFFFFFFF),
500: const Color(0xFFFFFFFF),
600: const Color(0xFFFFFFFF),
700: const Color(0xFFFFFFFF),
800: const Color(0xFFFFFFFF),
900: const Color(0xFFFFFFFF),
},
);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: materialWhite,
),
darkTheme: ThemeData.dark(),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
It was a struggle, but it finally looks right. (I wonder if there's a better way...)
After working so hard to prepare a white-toned AppBar...
Here is the next realization.
Part 4: It Takes Courage to Keep the AppBar Always Visible
Let's look at those famous iOS apps again.
The AppBar usually hides when you scroll, doesn't it? If there is a significant amount of content, I thought that keeping the AppBar always visible might not be the best idea.
In the recently popular book on Object-Oriented User Interface (commonly known as the OOUI book), it mentions that it's better to capture the user's object of interest and design simply (whether or not I correctly understood it is another story...).
With this in mind, if there's a certain amount of content, that content is the main focus. I felt it's better to hide things like the AppBar that occupy screen space as much as possible.
So, I recommend SliverAppBar. I love Sliver-system widgets because they look cool.
- Show the AppBar only when necessary.
- Think carefully about whether the AppBar is truly necessary for that app.
This is a lesson of my own.
Now, moving on. From here on, compared to what we've covered so far, these feel more like tips, but I'll explain them briefly as they are techniques I personally like.
Part 5: Round Off the Corners as Much as Possible
There's a line that Steve Jobs supposedly said (just the nuance):
Make rectangles have rounded corners, just like circles and ellipses. Most things in the world are like that.
I really like this.
I love furniture and tableware made of wood, and I believe the warmth of wood comes from the smooth texture created by craftsmen spending time and effort to polish the corners. As a "believer," I imagine that I want to feel that same kind of warmth in software.
Now, to round off corners in Flutter, you use Decoration.
body: Center(
child: Container(
width: 200,
height: 200,
),
),
Even a square container like this can be changed by setting a Decoration...
body: Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.lime,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
offset: Offset(10, 10),
color: Theme.of(context).scaffoldBackgroundColor,
blurRadius: 20,
),
BoxShadow(
offset: Offset(-10, -10),
color: Theme.of(context).scaffoldBackgroundColor,
blurRadius: 20,
),
],
),
),
),
The corners get rounded, giving it a nice feel.
While I don't think content that takes up the full width of the screen necessarily needs rounded corners, rounding the corners of content that appears isolated somewhere on the screen can create a gentle and polished atmosphere.
For example, in the Google News example I mentioned earlier, if you look closely, you can see that the corners of the photos are rounded.
Now, the last one.
Part 6: Try Using Stack Once in a While
If you combine Decoration and Stack like this and build it vigorously, you can achieve something like this. The positioning adjustment should probably be done a bit more properly... (sorry for being lazy).
body: Center(
child: Stack(
children: <Widget>[
Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.lime,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
offset: Offset(10, 10),
color: Theme.of(context).scaffoldBackgroundColor,
blurRadius: 20,
),
BoxShadow(
offset: Offset(-10, -10),
color: Theme.of(context).scaffoldBackgroundColor,
blurRadius: 20,
),
],
),
),
Container(
alignment: Alignment.center,
height: 100,
width: 90,
child: Container(
alignment: Alignment.center,
height: 50,
width: 90,
decoration: BoxDecoration(
color: Colors.white,
border: Border(),
borderRadius: BorderRadius.only(
topRight: Radius.circular(50),
bottomRight: Radius.circular(50),
),
),
child: Text(
'test',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
),
],
),
),
Using Stack well when you want to add a little bit of "flair" can give it a nice polished look.
Summary
I've introduced some tips for creating polished-looking apps in Flutter.
As I am still a Flutter beginner myself, I hope this will be helpful for those who want to start challenging themselves with Flutter from now on.
Also, why not try using the solo-developed app "Yaruhyaku" that I introduced at the beginning, register "Create a Flutter app" as one of your goals for 2021, and give it a shot?
(Apologies for the promotion at the very end.)
Well then.
Sugit ٩( ᐛ )و
Discussion
興味深くよみました。おっしゃるとおりFlutterで作るといかにもFlutterな感じがなんか気持ち悪いんですよね
特にiOSの設定画面のようなリストを作るときに感じます。ああCupertinoがいいなあ、、みたいな。
Material Design自体はとても良いものなので、iOSでの見た目も意識しながら上手く使ってFlutterの良さを活かせるようにしたいですね。私はいまだに苦戦することがしばしばあります( ᐛ )
筆者によるメモ
とかやっておけば白いAppBar手軽にできて楽では(白いというか透明)
Darkはこっち。