Things take time

[Flutter] 잡다한 플러터 이것저것(계속 수정/추가) 본문

Flutter

[Flutter] 잡다한 플러터 이것저것(계속 수정/추가)

겸손할 겸 2022. 5. 17. 10:01
  •  컨테이너 안에 있는 위젯들을 고려한, 현재 컨테이너 너비 구하기코드는 다음과 같다.
  Size _getSize(GlobalKey key) {
    if (key.currentContext != null) {
      final RenderBox renderBox =
          key.currentContext!.findRenderObject() as RenderBox;
      return renderBox.size;
    }
    return Size(0, 0);
  }
  • Stack의 사이즈는 Children중 가장 큰 위젯의 사이즈로 정해진다, 다만 Positioned처럼 지정되지 않은 Container가 있을 경우, 해당 컨테이너가 다른 위젯들 무시하고 해당 컨테이너가 모든것을 차지한다.
  • 앱을 처음 키자마자 회전을 원할 때는 다음과 같이 사용한다. ensureInitialized는 시스템에게 이후에 비동기작업이 있을 것임을 알리는 것이고, main()에서 사용할때만 사용하면 된다.
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.landscapeLeft])
      .then((_) {
    runApp(const MyApp());
  });
  • FlutterAppDelegate는 기존 AppDelegate +FlutterAppLifeCycleProvider의 추가 버전이다.
  • FlutterEngine은 플러터 화면을 네이티브에 띄울 때 사용되는 개념이다.
  • Command PhaseScriptExecution failed with a nonzero exit code. 오류: 문법오류, 플러터 쪽에서 살펴보거나 엑스코드에서 보여주는것 확인할 것

  • Failed to register observatory port with mDNS with error -65555. : info plist 추가
<key>NSBonjourServices</key>
<array>
    <string>_dartobservatory._tcp</string>
</array>
  • Widget 내에서 if문/for문을 사용해야할 경우 다음과 같이 사용할 수 있다. 2가지 방법(...[] / 혹은 []{}없이 사용)
            if (contentPanelList.length > 1) ...[
              for (ContentPanel contentPanel in contentPanelList) ...[
                Visibility(
                  visible: _isPortrait,
                  child: _setDataListView(),
                ),
              ]
            ],
            if (contentPanelList.length > 1)
              for (ContentPanel contentPanel in contentPanelList)
                Visibility(
                  visible: _isPortrait,
                  child: _setDataListView(),
                ),
  • Flutter에서 String -> DateFormat 은 다음과 과정을 거치는데, intl라이브러리를 사용한다. 또한, 라이브러리가 기본적으로 지원하는 Date형식이 있어서 아래에 해당되지 않는String값이라면 String의 값을 바꿔서, 해당 함수에서 이해할수 있게 해줘야한다.(?)
"2012-02-27 13:27:00"
"2012-02-27 13:27:00.123456789z"
"2012-02-27 13:27:00,123456789z"
"20120227 13:27:00"
"20120227T132700"
"20120227"
"+20120227"
"2012-02-27T14Z"
"2012-02-27T14+00:00"
"-123450101 00:00:00 Z": -12345년.
"2002-02-27T14:00:00-0500": 와 같음"2002-02-27T19:00:00Z"

만약 서버에서 20120227132700, 흔히 yyyyMMddhhmmss의 방식으로 내려줬다고 하자. 위의 Format중일치하는 것이 없으므로, 여기서 DateFormat('').parse(String)사용시 불가하다. 그러므로 가장 비슷한 20120227T132700 저 방식처럼 중간에 T라는 문자열을 넣어서 변환해줘야 정상적으로 작동한다.

import 'package:intl/intl.dart' as intl;
...


String onairDate = '202205261549';
String formattedString = onairDate.substring(0, 8) + 'T' + onairDate.substring(8, onairDate.length);
var formattedOnairDate = intl.DateFormat('yyyy.MM.dd HH:mm').format(DateTime.parse(formattedString)).toString();

이 얼마나 거지같은지, 좀 더 간단한건 없으려나.. 서버에서 자리수에 대해 확실히 내려준다면 난 그냥 문자열을 편집해서 사용한다. 

  • iOS 기기로 플러터를 디버그하면, 디버그 껐을 때 단독으로 쓸 수 없으므로 flutter run --release를 통해 상용 방식으로 디버그하여 확인한다.
  • ListView를 통해 위젯을 만들었을 때, 굉장히 버벅이는 것이 느껴질 땐 위 방법처럼 release를 통해 상용 방식으로 디버그하면 빨라진다. 디버깅이 느리다고 한다.

https://stackoverflow.com/questions/46761036/why-my-flutter-apps-listview-scroll-not-as-smooth-as-the-flutter-gallery-app

 

Why my flutter app's listview scroll not as smooth as the flutter gallery app?

I created a release build of my scroll demo app using Flutter. I wonder why the listview scrolling of my app is NOT as smooth as the flutter gallery app. I used LG G5 for this test. Here's a link...

stackoverflow.com

  • Flutter에서 안드로이드 match_parent, wrap_content의 대응 되는 개념은 각각, Container기준 사이즈를 max로 지정(double.infinite)하느냐 지정하지 않느냐이다. 따로 값을 지정하지 않으면, 안에 들어있는 위젯의 크기에 따라 각각 늘어난다.
  • Column, Row에서는 안에 들어오는 children들의 크기에 맞춰 자동적으로 늘어나거나 줄어들지만 리스트뷰와 같은 위젯(스크롤)이 들어올땐 지정해야한다. 예를 들어, Column에서 Text부분과 ListView가 들어올 경우, 두 위젯 모두 height가 지정되어 있지 않다면 에러가 발생한다. ListView를 부모의 나머지를 갖도록 Expand로 감싸고, 부모의 Height를 지정해야한다. 혹은 ListView를 SizeBox로 감싼 뒤, 리스트뷰의 크기를 지정한다.
  • 리스트 뷰를 쓸 때는 Height가 필요하다. 그러므로 Column, Row에서는 위의 방법을, 단독 사용시엔 height를 지정하자.
  • 빈 위젯, Empty widget을 리턴하고 싶을 땐 다음과 같이 사용한다.
SizedBox.shrink();
  • ShrinkWrap속성은 ScrollDirection의 속성을 가진 위젯(ListView, GridView, CustomScrillView)이 자신이 스크롤할 수 있는 범위를, Child인 위젯의 내용에 따라 결정되어야하는지에 대한 여부이다. default false의 경우엔 스크롤 방향 기준, 허용된 최대 사이즈를 가진다(부모 위젯의 사이즈 만큼). 만약 ScrollDirection의 속성을 가진 위젯이 제한되지 않은 제약조건(Expand..)을 가지고 있다면 true를 사용해야 unbounded height runtime exception이 나지않는다.
  • Column Children에서 height를 가진 위젯과 Expanded인 위젯이 있고, 그 Expanded 위젯 안에서 Column이 또 있고, 같은 방식으로 height를 가진위젯과 Expanded를 가진 위젯이 있을 때, Expanded위젯에 ListView가 있다면 문제가 되지 않는다. 그런데 위의 경우에서 ListView와 같은 Depth에 있는 height를 가진 위젯을 하나의 묶음으로 봐서 Column자체를 Scroll하고 싶다면 다음과 같이 해야한다.
    1. ListView의 shrinkWrap속성을 true로 변경한다. -> 즉, 자신의 부모위젯 사이즈 만큼을 가지는 것이 아니라 ListView안에 return되는 Widget의 사이즈만큼을 가지겠다고 변경한다.
    2. ListView의 physics를 NeverScrollableScrollphysics()로 변경한다. -> 리스트뷰 스크롤이 아니라 Column자체에서 스크롤을 해야하므로, 리스트 뷰 자체의 스크롤을 사용하지 않는다.
    3. ListView를 감싸고 있는 Expanded대신 Flexible로 변경한다. Expanded는 Flexible위젯에서 tight한 위젯이다. default인 loose로 변경함으로써 남아 있는 빈 공간을 하위 위젯인 리스트뷰의 크기만큼을 사용하겠다고 하는 것이다. 이때, 리스트뷰의 크기가 남은 빈 공간보다 크더라도 에러가 발생하지 않으며, 남아있는 부분 중 리스트뷰가 가질 수 있는 것만큼을 가진다. tight는 하위 위젯의 크기와 관련없이 남은 공간 자체를 가져가겠다는 의미이다.
    4. Column을 SingleChildScrollView로 감싸고, mainAxisSize를 min으로 한다.
    5. 참고: flexible은 shrinkWrap의 true, mainAxisSize.min과 쌍으로 맞고, expanded는 false와 맞다. (의미 다시 한 번 생각)
  • initState, didChangeDependencies 모두 처음 생성될 때 한 번 호출되지만, didChangeDependencies는 context에 접근할 수 있는 차이가 있다.
  • setState(){}를 호출할 때는 mounted를 체크하는 것이 좋다. setState(){}에만 쓰는 것은 번거로우므로 setState()를 override하여 if(mounted){ super.setState() } 를 사용하도록 하자.
  • 플러터에서 기본적으로 사용중인 위젯트리가 변경되지 않는다면 setState()를 해도 겉모양만 변할 뿐 내용은 변하지 않는다, 그런데 안의 child가 추가되거나 트리상 위치가 바뀌면, 다시 그리게 되는데 이를 방지하려면 재생성안되게 할 위젯에 key를 선언한다.(Unique Key사용 금지, setState할 때마다 키가 유니크하게 다시 생성되므로) 이렇게 되면, 해당 key값을 검사하여 같은 key를 가진 위젯이라면 다시 그리지 않고 재사용하게 된다.
  • TextButton의 패딩을 제거하기위해선 다음과 같이 사용한다.
            TextButton(
              style: TextButton.styleFrom(
                primary: Colors.white,
                tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                padding: EdgeInsets.zero,
              ),
              onPressed: () {
              },
              child: Icon(Icons.clear),
            ),
  • Key는 stateless, stateful 위젯 모두에서 사용할 수 있으며 위젯의 설정 값중 하나이다. 동일한 key값을 가진 위젯을 재배치할 경우, 해당 위젯은 재사용된다.
  • Stateless에서 동일한 위젯의 하위트리를 가진 상태에서 build가 다시 실행된다면, 해당 위젯들은 재사용 된다.(build는 호출되지만, 안의 내용들은 바뀌지 않음)
  • Stateless에서 위젯 앞에 const를 붙일 경우, 불필요한 build가 되지 않는다. 
  • Stack에서 위치가 바뀌는 위젯의 경우(Depth가 동일하나 배치가 바뀌는 경우) Key를 사용해야 재사용되면서 rebuild되지 않는다. => key의 위치는 재사용 되는 위젯 자체
  • Stack이 아닌 Depth자체가 바뀌는 위젯의 경우 Key를 사용해도 적용되지 않는다. Depth는 동일하되, 위젯의 배치가 바뀌는 경우엔 Key를 사용하고 Depth가 바뀌는 위젯의 경우에는 Key를 사용해도 재사용되지 않는다. 해당 Depth에 동일하게 위젯이 배치되어야한다. const사용은 sense.
  • Provider의 경우 상위에서 선언한(Consumer or ChangeNotifier)프로바이더의 생성자는 한 번 호출되나 build에서 해당 프로바이더에 다시 접근하는 인스턴스를 아래처럼 선언하는 경우, 생성자는 다시 호출되지 않으나 그 안에 사용되는 변수들은 다 초기화된다. 이유는 생성자는 상위 위젯에서 한번 선언하므로 한번만 호출되는 것이고, 이후 build에서 해당 인스턴스를 접근하게 되면 생성자에서 초기화 되지 않은 애들은 처음 선언된 그 변수로 초기화된다.
build(BuildContext context){
...

_providerChattingSendbird =
    Provider.of<ProviderChattingSendbird>(context, listen: true);
_providerDetailCommon =
    Provider.of<ProviderDetailCommon>(context, listen: true);
  • sizedbox vs container: 둘다 width, height, child를 가질 수 있지만, sizedbox는 const로 인식한다. 이 뜻은 화면을 다시 build하는 과정에서 const는 바뀌지 않으므로 sizedbox밑에 있는 위젯은 다시 그리지 않는다. 예를 들어, 채팅방을 만들었을 때 화면을 회전했고 그에 따라 rebuild하는 과정에서 채팅 뷰를 sizedbox 하위로 만들었으면 채팅이 유지되는 것으로 보일것이고 container의 경우 다시 그리기 때문에 채팅방 load부터 다시 수행한다. 그러므로 sizedbox의 사용을 자주하자.