0. 파일명 짓는 규칙

1. build

일반적으로 flutter app 을 빌드하는 명령어는 다음과 같다.

flutter build apk --release

그런데 조금 더 세분화 하여 빌드하고 싶다면 다음과 같이 옵션을 넣어준다면 더 최적화 된 app 을 받아볼 수 있다.

flutter build apk --target-platform android-arm --split-per-abi --release

그리고 명령어가 너무 기니까 다음과 같이 alias 를 만들어주면 된다. 물론 alias 명은 본인이 원하는 것으로 하면 된다.

alias fbar='flutter build apk --target-platform android-arm --split-per-abi --release'

2. splash 화면

splash 화면은 splash 라이브러리를 활용하여 손쉽게 작성할 수 있다.
다만, native 단에서 동작하기에 flutter 단에서 뭔가 로그인이 필요한 경우에 splash 화면이 flutter engine 에서도 필요로 하게 된다.
따라서 splash library 말고도 flutter 에 splash 화면을 직접 구현 해줘야한다.
이때, android 15 미만, iOS 같은 경우는 고해상도 이미지가 필수이다. 표출하고 싶은 img X4 의 해상도가 필요하다.

3. 화면 이동

app-lifecycle 의 initState 에서 화면이동을 하면 굉장히 위험하다.
따라서 initState 에서는 State 만 변경해주고, 화면이동을 하고싶은 경우엔 AfterLayoutMixin 을 State 에서 상속받으면 된다.

그리고 WidgetFlutterBindingAfterLayoutMixin 을 함께 사용하면 훨씬 더 자연스러운 화면을 연출할 수 있다.

4. back button

5. WillPopScope, PopScope

https://ch5c.tistory.com/2
https://velog.io/@jeongminji4490/Flutter-WillPopScope-Deprecated

6. List 와 Set 을 변환하여 자유자재로 이용하기

7. 당겨서 새로고침

RefreshIndicator

8. 전역에서 사용 가능한 상수

class TtossAppBar extends StatefulWidget {
  // 여기에 static const 로서 사용된.
  static const double appBarHeight = 60;

  const TtossAppBar({super.key});

  
  State<TtossAppBar> createState() => _TtossAppBarState();
}

class _TtossAppBarState extends State<TtossAppBar> {
  bool _showRedDot = false;

9. slivers

flutter 다양한 객체(조각)들을 list 화 하여 보여줄 수 있도록 하는 것.
예를 들어 알람에서 다양한 형태의 알람들을 표출하는 것.

class _NotificationScreenState extends State<NotificationScreen> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          const SliverAppBar(
            title: Text("알림"),
            pinned: true,
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) => NotificationItemWidget(
                notification: notificationDummies[index],
                onTap: () {
                  NotificationDialog([notificationDummies[0], notificationDummies[1]]).show();
                },
              ),
              childCount: notificationDummies.length,
            ),
          )
        ],
      ),
    );
  }
}

10. Theme 가 깨지는 경우

부모 위젯에 Material 이 설정이 안 되어서 그렇다.
따라서 Material 이나 Scaffold 로 감싸주면된다.

class _NotificationScreenState extends State<NotificationScreen> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          const SliverAppBar(
            title: Text("알림"),
            pinned: true,
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) => NotificationItemWidget(
                notification: notificationDummies[index],
                onTap: () {
                  NotificationDialog([notificationDummies[0], notificationDummies[1]]).show();
                },
              ),
              childCount: notificationDummies.length,
            ),
          )
        ],
      ),
    );
  }
}

11. localization

context.locale.languageCode 를 통하여 ko 혹은 en 과 같은 현재 언어 코드를 가져올 수 있다.

12. notification

super.animation 를 통해 해당 위젯이 어디서 나올지 이러한 애니메이션을 정할 수 있다.
super.barrierDismissible 를 통해 backdrop 을 눌렀을 때 해당 위젯이 pop 될지 말지 정할 수 있다.
onTap 에서 widget.hide() 를 통해 위젯을 넣거나 뺄 수 있는데 Nav.pop(context); 를 통해서도 가능하다.
하지만 widget.hide() 를 사용하는 것을 권장한다.

class NotificationDialog extends DialogWidget {
  final List<TtossNotification> notifications;

  NotificationDialog(this.notifications,
      {super.key, super.animation = NavAni.Bottom, super.barrierDismissible = false});

  
  DialogState<NotificationDialog> createState() => _NotificationDialogState();
}

class _NotificationDialogState extends DialogState<NotificationDialog> {
  
  Widget build(BuildContext context) {
    return Material(
      type: MaterialType.transparency,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          ...widget.notifications
              .map((element) => NotificationItemWidget(
                  onTap: () {
                    widget.hide();
                  },
                  notification: element))
              .toList()
        ],
      ),
    );
  }
}

13. _tabController

SingleTickerProviderStateMixin

class StockFragment extends StatefulWidget {
  const StockFragment({Key? key}) : super(key: key);

  
  State<StockFragment> createState() => _StockFragmentState();
}

class _StockFragmentState extends State<StockFragment> with SingleTickerProviderStateMixin {
  late final _tabController = TabController(length: 2, vsync: this);
  
  
  // build 함수..

위 코드에서 late 가 없으면 안 된다.
final 은 stateFullWidget 이 생성됨과 동시에 작동하는데 이때, this 는 아직 생성되지 않았기 때문에 동작하지 않는다.
대신 initState 함수를 생성하여 넣어줘도 되는데 코드가 복잡해지니 쉽게 late 예약어를 사용하여 위 코드에서는 tabController 가 추후 사용될 때 초기화를 하면 되기 때문에 정상적으로 동작한다.

14. stless 에서 context 에 접근하기

class MyStockFragment extends StatelessWidget {
  const MyStockFragment({super.key});

  
  Widget build(BuildContext context) {
    return Column(
      children: [getMyAccount(context), height20, getMyStock(context)],
    );
  }

  Widget getMyAccount(BuildContext context) => Container(
    padding: const EdgeInsets.symmetric(horizontal: 20),
    color: context.appColors.roundedLayoutBackground,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      // 생략

15. class 의 관하여(mixin)

아래 코드는 named parameter 를 적용시킨 코드이다.
그리고 super.name 에는 super(name) 을 통해 부모 클래스인 SimpleStock의 생성자도 호출한다.

class SimpleStock {
  final String name;

  SimpleStock(this.name);

  factory SimpleStock.fromJson(dynamic json) {
    return SimpleStock(json['name']);
  }
}
class PopularStock extends SimpleStock with StockPercentageDataProvider {
  
  final int yesterdayClosePrice;
  
  final int currentPrice;

  PopularStock({
    required String name,
    required this.yesterdayClosePrice,
    required this.currentPrice,
  }) : super(name);
}
class Stock extends PopularStock {
  final String stockImagePath;

  Stock({
    required super.name,
    required super.yesterdayClosePrice,
    required super.currentPrice,
    required this.stockImagePath,
  });
}
abstract mixin class StockPercentageDataProvider {
  int get currentPrice;

  int get yesterdayClosePrice;

  double get todayPercentage =>
      ((currentPrice - yesterdayClosePrice) / yesterdayClosePrice * 100).toPrecision(2);

  String get todayPercentageString => _isPlus ? '+$todayPercentage%' : '$todayPercentage%';

  bool get _isPlus => currentPrice > yesterdayClosePrice;

  bool get _isSame => currentPrice == yesterdayClosePrice;

  Color getTodayPercentageColor(BuildContext context) {
    if (_isSame) {
      return context.appColors.dimmedText;
    } else if (_isPlus) {
      return context.appColors.plus;
    } else {
      return context.appColors.minus;
    }
  }
}

mixin 에서 사용되는 필드값들은 절대적으로 unique 해야한다.
그렇지 않으면 무시될 수 있다.

16. GetX (Obx widget)

그냥 List 말고 RxList 를 사용하면 좋다. => 참고로 Rx 만 사용한다면 import 할 때, Get을 전부 가져오지 말고 GetrX만 가져오면 더 좋다.
동적 List 이기 때문이다. 이 RxList 는 GetX 에서 제공한다.
이때 [] 뒤에 반드시 .obs 가 붙어야한다.
참고로 obsobservation(관찰) 의 뜻을 지니고 있다.

onInitinitState 와 같이 GetxController 가 최초에 생성이 될 때 호출되는 함수이다.

class StockSearchData extends GetxController {
  List<SimpleStock> stocks = [];
  RxList<String> searchHistoryList = <String>[].obs;
  RxList<SimpleStock> searchResult = <SimpleStock>[].obs;

  
  void onInit() {
    searchHistoryList.addAll(['삼성전자', 'LG전자', '현대차', '넷플릭스']);
    () async {
      stocks.addAll(await LocalJson.getObjectList("json/stock_list.json"));
    }();
    super.onInit();
  }

  void search(String text) {
    if (isBlank(text)) {
      searchResult.clear();
      return;
    }
    searchResult.value = stocks.where((element) => element.name.contains(text)).toList();
  }

  void addSearchHistory(String stockName) {
    searchHistoryList.insert(0, stockName);
  }
}
  • Obx 위젯은 성능도 더 뛰어난데,
    Rx 변수가 변할 때 builder 가 전체가 실행되는 것이 아닌, Obx 함수만 재실행 된다.

17. Facotry 생성자 simple method

아래 코드에서 두 fromJson 함수의 차이는 무엇일까?

class SimpleStock {
  final String name;

  SimpleStock(this.name);
  
  // 1.
  SimpleStock fromJson(dynamic json) {
    return SimpleStock(json['name']);
  }

  // 2. 
  factory SimpleStock.fromJson(dynamic json) {
    return SimpleStock(json['name']);
  }
}
  1. 은 일반 메서드 fromJson
  2. 팩토리 생성자 factory SimpleStock.fromJson 이다.

1번은 반드시 객체가 생성된 후에 사용가능한 일반 메서드이고, 2번은 객체를 생성하는 동시에 JSON 데이터를 처리할 수 있다.

factory 키워드를 사용한 생성자는 새로운 인스턴스를 반환하거나, 조건에 따라 기존의 인스턴스를 반환할 수 있는 특별한 생성자이다.
즉, 이 생성자는 SimpleStock 클래스의 인스턴스를 생성할 때 직접 호출할 수 있다.

void main() {
  SimpleStock newStock = SimpleStock.fromJson({'name': 'AAPL'});
  print(newStock.name); // "AAPL"
}
  • 장점:
  1. 인스턴스 재사용: 이미 생성된 객체를 반환할 수 있어 메모리 사용을 줄일 수 있습니다.
  2. 조건부 생성: 생성 과정에서 특정 조건에 따라 다른 클래스의 인스턴스를 반환할 수 있습니다.
  3. 유연성: 객체 생성 로직을 캡슐화하여 더 복잡한 초기화 과정을 지원할 수 있습니다.

18. ListView(children:[])

ListView(children:[]) 은 children 이 300 ~ 400 개가 넘어가면 성능이슈가 발생한다.
따라서 ListView.builder 를 사용하는 것이 더 좋은 선택이다.

19.

긴 변수가 두번 들어가면 가독성이 매우 떨어지므로 람다식=>을 해제후
stockName 이라는 변수를 fn 안에 선언하여 효율적으로 간단하게 작성해주면 된다.

20.

이 코드의 문제점은 생성자 함수가 실행된 후 initState 가 실행되기 때문에 그냥 아래 코드처럼 작성하면 serchData 에는 아무것도 들어가지 않게 된다.
따라서 life-cycle 을 잘 인지하고 late 예약어를 붙여서 추후 값이 들어갈 수 있도록 해야한다.

21. 반환타입

자식 Widget 에서 어떠한 값을 반환받아야 한다면 (예를 들어 설정에서 날짜를 선택한다든지, 값을 입력 받는다든지)
뒤에 <> 으로 반환 타입을 지정해주면 된다.

class NumberDialog extends DialogWidget<int> {
  NumberDialog({super.key, super.animation = NavAni.Fade, super.barrierDismissible = false});

  @override
  DialogState<NumberDialog> createState() => _NotificationDialogState();
}

class _NotificationDialogState extends DialogState<NumberDialog> {
  final controller = TextEditingController();
  // 생략

22. opensource

flutter pub run flutter_oss_licenses:generate.dart -o assets/json/licenses.json --json
위 명령어를 터미널에 입력하면 해당 프로젝트에서 사용하는 lisence 관련 문서가 생성된다.
이를 파싱해서 표출하면 된다.

23. EvaIcons

그냥 아이콘 보다 더 이쁜 아이콘들을 모아놓은 객체이다.

24. mounted

해당 위젯이 살아있는지 파악하는 키워드

profile
智(지)! 德(덕)! 體(체)!

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN