κΈ°λ³Έμ μΌλ‘ Dart Style Guideλ₯Ό λ°λ₯΄κ³ μμ§λ§, λ³Έ λ¬Έμμμλ Flutterμ μ‘°κΈ λ νΉνλ μ€νμΌ κ°μ΄λλ₯Ό μμ±ν©λλ€.
- μ½λ μ€νμΌ
- νΉμν κΈ°λ₯μ κ°μ§κ³ μλ ν¨μ, λ©μλ λ° μμ±μλ₯Ό μ¬μ©ν©λλ€
- var λλ dynamic μ¬μ©μ νΌν©λλ€
- μμ(Constant)μ Scopeλ₯Ό μ΅μν ν©λλ€
- λͺ ννμ§ μμ μ«μ λλ Magic Numberμ μ¬μ©μ νΌν©λλ€
- initState() μ dispose() ν¨μλ₯Ό Override νλ κ²½μ°,
super
νΈμΆ μμλ₯Ό μ£Όμν΄μ μ¬μ©ν©λλ€
- λ€μ΄λ°
- ν¬λ§€ν
- ν΄λμ€ λ©€λ²λ€μ μλ―Έκ° μκ² μ λ ¬νμΈμ
- μμ±μ ꡬ문
- μ΅λ 80μμ μ€ κΈΈμ΄λ₯Ό μ νΈν©λλ€
- μ¬λ¬μ€μ μΈμ λ° νλΌλ―Έν° νλͺ©λ€μ 2μΉΈμ© λ€μ¬μ°κΈ° ν©λλ€
- μ¬λ κ΄νΈ
(
λ€μ μ€λ°κΏμ΄ μλ κ²½μ°, λ«λ κ΄νΈ)
μμλ μ€λ°κΏμ μΆκ°ν©λλ€ - μΈμλ€κ³Ό νλΌλ―Έν° 리μ€νΈ λλ List νμμ νκΈ°ν λ Multi-line μΌλ‘ μμ±νλ κ²½μ°λΌλ©΄, κ° λΌμΈμ λμ μΌν
,
λ₯Ό μΆκ°ν©λλ€
- Packages
μ λ°μ μΈ Flutter κ΄λ ¨ μ€νμΌμ λν΄ μμ±ν©λλ€. νΉμ ν¨ν€μ§ κ΄λ ¨ μ€νμΌμ Packages λ¬Έλ¨μμ μμ±ν©λλ€.
μ¬λ¬ μ΅μ μ΄ μλ κ²½μ°, κ°μ₯ κ΄λ ¨μ±μ΄ λμ μμ±μλ λ©μλλ₯Ό μ¬μ©νμΈμ.
// BAD:
const EdgeInsets.TRBL(0.0, 8.0, 0.0, 8.0);
// GOOD:
const EdgeInsets.symmetric(horizontal: 8.0);
λͺ¨λ λ³μλ€(variables)κ³Ό μΈμλ€(arguments)μ νμ μ΄ μ‘΄μ¬ν©λλ€. μ€μ μ νμ νμ ν μ μλ κ²½μ°μλ dynamic λλ Object νμ μ μ¬μ©μ νΌνμΈμ. κ°λ₯ν κ²½μ° λͺ¨λ Generic νμ μ μ νμ λͺ μν κ²μ κΆμ₯ν©λλ€. Closure μμ μ¬μ©λλ Parameter λν νμ μ΄ λͺ μλμ΄μΌ ν©λλ€.
λͺ¨λ κ²½μ°μμ var κ³Ό dynamic μ¬μ©μ νΌνμΈμ. λ§μ½, νμ μ μ μ μλ κ²½μ°λΌλ©΄ Object(λλ Object?)λ₯Ό μ¬μ©νμ¬ Casting ν©λλ€. μ΄λ κ² νλ μ΄μ λ dynamic μ κ²½μ° λͺ¨λ μ μ κ²μ¬κ° λΉνμ±νλκΈ° λλ¬Έμ λλ€.
κΈλ‘λ² μ μ μμλ₯Ό μ¬μ©νλ λμ , κ΄λ ¨μ±μ΄ μλ ν΄λμ€μμ λ‘컬 const λλ static const λ₯Ό μ¬μ©νμΈμ. λ§μ½, μ μΈν μμκ° λ§μμ§λ€λ©΄ abstract final class λ‘ λννλκ²μ κΆμ₯ν©λλ€.
μ½λμμ μ«μ λ° ν μ€νΈμμ μ¬μ©λλ λͺ¨λ μ«μμ κ²½μ° λͺ ννκ² μ΄ν΄ν μ μμ΄μΌ ν©λλ€. λ§μ½, μ«μμ μΆμ²κ° λͺ ννμ§ μμ κ²½μ° ννμμ κ·Έλλ‘ λκ±°λ λͺ νν μ£Όμμ μΆκ°νμΈμ.
μμ:
// BAD:
const double kMaxWidth = 1200;
// GOOD:
/// κΈ°λ³Έ κ° 60μ ν΄μλ μ‘°μ μ μν΄ 20μ κ³±ν κ°
const double kMaxWidth = 60 * 20;
initState() μ dispose() ν¨μλ₯Ό Override νλ κ²½μ°, super
νΈμΆ μμλ₯Ό μ£Όμν΄μ μ¬μ©ν©λλ€
initState() μ κ²½μ° super.initState() λ₯Ό νΈμΆ ν ν μΆκ°μ μΈ μμ μ μ§ννμΈμ. λ°λ©΄, dispose() μ κ²½μ° super.dispose() λ₯Ό νΈμΆ νκΈ° μ μ μΆκ°μ μΈ μμ μ μ§ννμΈμ.
initState() μ κ²½μ° μμ ―μ μ΄κΈ°ν κ³Όμ μμ μ€μν μλͺ μ£ΌκΈ° νΈμΆμ λλ€. μ΄λ₯Ό λ¨Όμ νΈμΆν΄μΌ μμ ―μ μνκ° μμ μ μΌλ‘ μ μ§λ©λλ€.
μμ:
const double kMaxWidth = 1200;
const Duration kAnimationDuration = Duration(milliseconds: 200);
κ·Έλ¬λ, κ°λ₯ν κ²½μ° kDefaultButtonColor μ κ°μ΄ μ μ μμλ₯Ό μ μΈνλ λμ , Button.defaultColor μ κ°μ΄ ν΄λμ€ λ΄λΆμ μμλ₯Ό μ μΈνμΈμ. νμν κ²½μ° abstract final class λ₯Ό μ¬μ©νμ¬ μμλ₯Ό μ μΈν©λλ€.
initState μ dispose μ²λΌ λͺ νν λΌμ΄νμ¬μ΄ν΄μ΄ μλ κ²½μ°, initState κ° dispose λ³΄λ€ λ¨Όμ μ€λλ‘ μ λ ¬νμΈμ.
μμ:
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
@override
void initState() {
super.initState();
}
@override
void build(BuildContext context) {
}
@override
void dispose() {
super.dispose();
}
}
λ§μ½, μμκ° λͺ ννμ§ μμ κ²½μ°μλ μλμ κ°μ μμλ₯Ό λ°λ₯΄λ©°, κ° μμ μ¬μ΄μλ λΉ λΌμΈμ μΆκ°ν©λλ€.
- μμ±μμμ μ΄κΈ°νλλ final λ³μλ€
- μμ±μ : μμ±μλ κΈ°λ³Έ μμ±μλ₯Ό λ¨Όμ μ μΈνκ³ , μΆκ°μ μΈ μμ±μλ κ·Έ λ€μ μ μΈν©λλ€.
- ν΄λμ€μ κ°μ μ νμ μμ
- ν΄λμ€μ λμΌν μ νμ λ°ννλ μ μ λ©μλλ€
- λ€λ₯Έ μ μ λ©μλλ€
- μ μ μμ±λ€(properties)κ³Ό μμλ€
- λ³κ²½ κ°λ₯ν μμ±λ€(properties)μ λΉ λΌμΈ κ΅¬λΆ μμ΄ λ€μκ³Ό κ°μ μμλ‘ μμ±ν©λλ€.
- getter
- private field
- setter
toString
κ³Όbuild
λ₯Ό μ μΈν λ©μλλ€build
λ©μλhashCode
,toString
λ©μλ
μμ±μκ° μ¬λ¬ νλλ₯Ό λ°κ³ μλ€λ©΄, ν΄λΉνλ ν΄λμ€ λ©€λ² λ³μλ€μ μ μΈ μμλ λμΌν΄μΌ ν©λλ€.
μ΄κΈ°ν λͺ©λ‘μμ super()
λ₯Ό νΈμΆνλ κ²½μ°, μμ±μμ μ½λ‘ , κ·Έλ¦¬κ³ super ν€μλ μ¬μ΄μ 곡백μ μ μ§ν©λλ€. super class μ μ λ¬ν μΈμκ° μμΌλ©΄, super λ₯Ό νΈμΆνμ§ λ§μΈμ.
μμ:
// one-line constructor example
class ConstantTween<T> extends Tween<T> {
ConstantTween(T value) : super(begin: value, end: value);
// ...
}
// fully expanded constructor example
class ConstantTween<T> extends Tween<T> {
ConstantTween(
T value,
) : super(
begin: value,
end: value,
);
// ...
}
μ΅λ μ€ κΈΈμ΄λ 80μλ₯Ό λͺ©νλ‘ νμ§λ§, μ€μ λλμμ λ μ½κΈ° μ΄λ €μμ§κ±°λ, μ€κ³Ό μ£Όλ³ μ€μ μΌκ΄μ±μ΄ λ¨μ΄μ§λ κ²½μ°μλ ν΄λΉ 쑰건μ 무μνκ³ μμ±ν©λλ€.
// BAD (breaks after assignment operator)
final List<FooBarBaz> _members =
<FooBarBaz>[const Quux(), const Qaax(), const Qeex()];
// BETTER (only slightly goes over 80 chars)
final List<FooBarBaz> _members = <FooBarBaz>[const Quux(), const Qaax(), const Qeex()];
// BETTER STILL (fits in 80 chars)
final List<FooBarBaz> _members = <FooBarBaz>[
const Quux(),
const Qaax(),
const Qeex(),
];
μμ:
Foo f = Foo(
bar: 1.0,
quux: 2.0,
);
μ¬λ κ΄νΈ λ€μ μ€λ°κΏμ΄ μλ κ²½μ°, λ«λ κ΄νΈ μμλ μ€λ°κΏμ μΆκ°ν©λλ€
μ¬λ κ΄νΈ (
λ€μ μ€λ°κΏμ΄ μλ κ²½μ°, λ«λ κ΄νΈ )
μμλ μ€λ°κΏμ μΆκ°ν©λλ€
μμ:
// BAD:
foo(
bar, baz);
foo(
bar,
baz);
foo(bar,
baz
);
// GOOD:
foo(bar, baz);
foo(
bar,
baz,
);
μΈμλ€κ³Ό νλΌλ―Έν° 리μ€νΈ λλ List νμμ νκΈ°ν λ Multi-line μΌλ‘ μμ±νλ κ²½μ°λΌλ©΄, κ° λΌμΈμ λμ μΌνλ₯Ό μΆκ°ν©λλ€
μμ:
List<int> myList = [
1,
2,
];
myList = <int>[3, 4];
foo1(
bar,
baz,
);
foo2(bar, baz);
νλͺ© μ€ νλκ° multi-line callback, collection literal λλ switch ννμμΈ κ²½μ° λμ μΌν μμ΄ μΆκ°ν μ μμ΅λλ€.
μμ:
// GOOD:
foo(
bar,
baz,
switch (value) {
true => ScrollDirection.forward,
false => ScrollDirection.reverse,
null => ScrollDirection.idle,
},
);
// also GOOD:
foo(bar, baz, switch (value) {
true => ScrollDirection.forward,
false => ScrollDirection.reverse,
null => ScrollDirection.idle,
});
// The same applies to collection literals and callbacks:
foo(<String>[
'list item 1',
'list item 2',
'list item 3',
]);
Future.delayed(Durations.short1, () {
if (mounted && _shouldOpenDrawer) {
_drawerController.forward();
}
});
λͺ¨λ κ²μ νμ€λ‘ μμ±ν μ§, Multi-line μΌλ‘ μμ±ν μ§λ λ―Έμ μΈ μ νμ΄λ©°, κ°μ₯ μ½κΈ° μ¬μ΄ λ°©λ²μ μ νν©λλ€. λ€λ§, μ°μλ 리μ€νΈμμλ ν΅μΌλ μ€νμΌμ μ¬μ©ν κ²μ κΆμ₯ν©λλ€.
μμ:
// BAD (because the second list is unnecessarily and confusingly different than the others):
List<FooBarBaz> myLongList1 = <FooBarBaz>[
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
];
List<Quux> myLongList2 = <Quux>[ Quux(1), Quux(2) ];
List<FooBarBaz> myLongList3 = <FooBarBaz>[
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
];
// GOOD (code is easy to scan):
List<FooBarBaz> myLongList1 = <FooBarBaz>[
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
];
List<Quux> myLongList2 = <Quux>[
Quux(1),
Quux(2),
];
List<FooBarBaz> myLongList3 = <FooBarBaz>[
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
FooBarBaz(one: firstArgument, two: secondArgument, three: thirdArgument),
];
λ€λ§, =>
λ₯Ό μ¬μ©νμ λ λͺ¨λ λ΄μ©μ΄ νμ€λ‘ μμ±μ΄ κ°λ₯ν κ²½μ°μλ§ μ¬μ©ν©λλ€.
μμ:
// BAD:
String capitalize(String s) =>
'${s[0].toUpperCase()}${s.substring(1)}';
// GOOD:
String capitalize(String s) => '${s[0].toUpperCase()}${s.substring(1)}';
String capitalize(String s) {
return '${s[0].toUpperCase()}${s.substring(1)}';
}
=>
μ μ¬μ©μ 리ν°λ΄ λλ switch ννμμ λ°ννλ getter λλ callback μμλ§ μ¬μ©ν©λλ€
μμ:
// GOOD:
List<Color> get favorites => <Color>[
const Color(0xFF80FFFF),
const Color(0xFF00FFF0),
const Color(0xFF4000FF),
_mysteryColor(),
];
// GOOD:
bool get isForwardOrCompleted => switch (status) {
AnimationStatus.forward || AnimationStatus.completed => true,
AnimationStatus.reverse || AnimationStatus.dismissed => false,
};
μ½λκ° return
λͺ
λ Ήλ¬Έλ§ ν¬ν¨νλ inline closureλ₯Ό μ λ¬νλ κ²½μ°μλ =>
λ₯Ό μ¬μ©ν©λλ€.
μ΄λ° κ²½μ°μλ ]
, }
λλ )
κ΄νΈλ callback μ΄ μμνλ μ€κ³Ό κ°μ λ€μ¬μ°κΈ°λ₯Ό κ°μ΅λλ€.
μμ:
// GOOD, but slightly more verbose than necessary since it doesn't use =>
@override
Widget build(BuildContext context) {
return PopupMenuButton<String>(
onSelected: (String value) { print('Selected: $value'); },
itemBuilder: (BuildContext context) {
return <PopupMenuItem<String>>[
PopupMenuItem<String>(
value: 'Friends',
child: MenuItemWithIcon(Icons.people, 'Friends', '5 new'),
),
PopupMenuItem<String>(
value: 'Events',
child: MenuItemWithIcon(Icons.event, 'Events', '12 upcoming'),
),
];
}
);
}
// GOOD, does use =>, slightly briefer
@override
Widget build(BuildContext context) {
return PopupMenuButton<String>(
onSelected: (String value) { print('Selected: $value'); },
itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
PopupMenuItem<String>(
value: 'Friends',
child: MenuItemWithIcon(Icons.people, 'Friends', '5 new'),
),
PopupMenuItem<String>(
value: 'Events',
child: MenuItemWithIcon(Icons.event, 'Events', '12 upcoming'),
),
]
);
}
Flutter Framework κ΄λ ¨ ν¨ν€μ§λ₯Ό import ν΄μΌνλ κ²½μ° λ€μκ³Ό κ°μ κΈ°μ€μΌλ‘ μμ±ν©λλ€.
- material : λλΆλΆμ μ± κ°λ°μ μ νν΄μ μ¬μ©
- cupertino : iOS μ€νμΌ μ±μ΄λ νλ«νΌ νΉνλ iOS UIλ₯Ό μ 곡ν λ μ¬μ©
- foundation : UI μμ ― λμ Flutterμ κΈ°λ³Έ κΈ°λ₯μ΄λ μν κ΄λ¦¬λ₯Ό μν ν΄λμ€λ§ νμν λ μ¬μ©
λνμ μΈ μνκ΄λ¦¬ ν¨ν€μ§ μ€ νλλ‘ riverpod μ μ¬μ©νκ³ μμ΅λλ€.
μμ§ initialized λμ§ μμκΈ°μ ref
λ± κΈ°ν νλ‘νΌν°λ€μ μ¬μ©ν μ μμΌλ―λ‘, Notifierμλ μμ±μκ° μμ΄μΌ ν©λλ€.
λμ build
λ©μλμ λ‘μ§μ μμ±ν©λλ€.
class MyNotifier extends ... {
// BAD:
MyNotifier() {
state = AsyncValue.data(42);
}
// GOOD:
@override
Result build() {
state = AsyncValue.data(42);
}
}
μ½λ μμ± λꡬ μ€ νλμΈ build_runner
ν¨ν€μ§λ Dart μ½λλ₯Ό μ¬μ©νμ¬ νμΌμ μμ±νλ λ°©λ²μ μ 곡νλ ν¨ν€μ§μ
λλ€. λ°λ³΅μ μΈ μμ
μ μλν νκΈ°λ νλ©°, Static Metaprogramming μ κ°λ₯νκ² ν΄μ€λλ€. (Dart μΈμ΄μ κ²½μ° μ΄ν리μΌμ΄μ
μ μ»΄νμΌ
νλλ° μΆκ° λ¨κ³κ° νμνλ€λ λ¨μ μ΄ μμ΄ μ΄λ₯Ό 보μνκΈ° μν΄ μ¬μ©ν©λλ€.)
λ§μ ν¨ν€μ§λ€μ΄ build_runner λ₯Ό νμ©νμ¬ μ½λ μμ±μ ν΅ν΄ κ°λ° μμ°μ±μ λμ΄κ³ μμ΅λλ€. μλ₯Ό λ€μ΄, freezed ν¨ν€μ§λ λ°μ΄ν° ν΄λμ€λ₯Ό μμ±νλλ° μ¬μ©λκ³ , json_serializable ν¨ν€μ§λ JSON μ§λ ¬ν μ½λλ₯Ό μμ±νλλ° μ¬μ©λ©λλ€.
μ΄μ²λΌ λ§μκ³³μμ μ¬μ©νλ λ§νΌ νλ² μ½λμμ±μ νκ² λλ©΄ λ§μ νμΌλ€μ΄ μμ±λ©λλ€. μ΄λ¬ν νμΌλ€μ νλ‘μ νΈ λ΄μμ μΆμ μ΄ μ΄λ ΅κΈ° λλ¬Έμ νλ‘μ νΈ λ΄μμ νΉμ ν΄λμ μμΉνλλ‘ κ΄λ¦¬ν©λλ€.
μμ:
project_root
βββ build/
β βββ ...
βββ lib/
βββ ...