반응형
반응형

📝API 통신해 데이터 가져오기

import 'dart:convert'; // JSON 디코딩을 위해 필요
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; // HTTP 패키지

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('HTTP API Example with FutureBuilder'),
        ),
        body: PostScreen(),
      ),
    );
  }
}

class PostScreen extends StatelessWidget {
  // 데이터를 가져오는 비동기 함수
  Future<Map<String, dynamic>> fetchPost() async {
    final response =
        await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));

    if (response.statusCode == 200) {
      // API 응답이 성공적이면 데이터를 파싱합니다.
      return json.decode(response.body);
    } else {
      // 응답이 실패하면 에러 처리
      throw Exception('Failed to load post');
    }
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Map<String, dynamic>>(
      future: fetchPost(), // 비동기 데이터를 가져오는 Future
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          // 데이터가 로딩 중일 때
          return Center(child: CircularProgressIndicator());
        } else if (snapshot.hasError) {
          // 에러가 발생한 경우
          return Center(child: Text('Failed to load data'));
        } else if (snapshot.hasData) {
          // 데이터를 성공적으로 가져온 경우
          final postData = snapshot.data!;
          return Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Title: ${postData['title']}',
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 16),
                Text(
                  'Body: ${postData['body']}',
                  style: TextStyle(fontSize: 16),
                ),
              ],
            ),
          );
        } else {
          // 데이터를 못 가져온 경우 대비
          return Center(child: Text('No data found'));
        }
      },
    );
  }
}

FutureBuilder를 통해 데이터를 받아서 화면에 출력할 수 있습니다. snapshot을 이용해 데이터 로딩 등을 처리할 수 있습니다.

 

📝Flutter 컨트롤러

final TextEditingController _usernameController = TextEditingController();

// 메모리 누수 잡기 [필수]
@override
void dispose() {
  _usernameController.dispose(); // 컨트롤러 dispose를 제때 사용 안하면 메모리 누수 발생
  super.dispose(); // 컨트롤러 관한 걸 제거한 후에 상위 것들도 dispose 한다 [ 일반적으로 이렇게 많이 사용함]
}

@override
void initState() {
  super.initState();

	// 텍스트 쓸때마다 감지한다 [ 상태관리 비슷한 개념 ]
  _usernameController.addListener(() {
    setState(() {
      _username = _usernameController.text; // 텍스트 필드의 값을 가져온다
    });
  });
}

TextField(
  controller: _usernameController, // 컨트롤러
	...
)
  • 컨트롤러의 경우 위젯이 로직을 처리할 수 있게 도와주는 역할을 한다.
  • 반드시 사용을 한 이후에는 dispose로 메모리 누수를 잡아줘야한다
  • addListener로 연동시킨 Widget의 변화가 있을 때마다 감지해서 이벤트 처리를 줄 수 있다

 

📝상태관리 (Provider)

// main.dart
final preferences = await SharedPreferences.getInstance();
final repository = PlaybackConfigRepository(preferences);

runApp(MultiProvider(
  providers: [ // 프로바이더에 쓰일 것들...
    ChangeNotifierProvider( // 프로바이더 지원 함수 [데이터 변경 감지 및 데이터 관리 해줌 + UI업데이트 등]
      create: (context) => PlaybackConfigViewModel(repository), // 해당 뷰모델을 프로바이더로 쓰겠다
    )
  ],
  child: const TikTokApp(),
));

// ViewModel 선언
...
class PlaybackConfigViewModel extends ChangeNotifier 

// View에서 ViewModel을 통해 로직 실행및 가져오기
SwitchListTile.adaptive(
	value: context.watch<PlaybackConfigViewModel>().muted,
	onChanged: (value) =>
	    context.read<PlaybackConfigViewModel>().setMuted(value),
	title: const Text("Mute video"),
	subtitle: const Text("Video will be muted by default."),
),

MVVM 기반으로 개발하며 Proivder를 이용해 데이터를 관리할 수 있다. main에서 provider를 사용하고 viewmodel을 연결해서 감지시키고 모든 곳에서 접근해서 사용이 가능하다. Riverpod이 더 많은 기능을 제공하고 더 잘 분리되어있다.

 

  • context.watch<T>()
    • T의 데이터 값이 변경되었을 때 위젯을 재빌드한다.
  • context.read<T>()
    • T의 데이터 값이 변경되었을 때 위젯을 재빌드하지 않는다.

 

📝Flutter 애니메이션

Animation 효과 줄 수 있는 위젯 (setState 필수) [비추] → 전체 랜더링 비효율적

void _onTap() {
    setState(() {
      _isSelected = !_isSelected;
    });
  }

GestureDetector(
	onTap: _onTap,
	child: AnimatedContainer( // 애니메이션 위젯 컨테이너
    duration: const Duration(milliseconds: 300), // 실행 시간
    padding: const EdgeInsets.symmetric(         // 컨테이너 패딩
      vertical: Sizes.size16,
      horizontal: Sizes.size24,
    ),
    decoration: BoxDecoration( // 컨테이너 꾸미기
      color: _isSelected
          ? Theme.of(context).primaryColor
          : isDarkMode(context)
              ? Colors.grey.shade700
              : Colors.white,
      borderRadius: BorderRadius.circular(
        Sizes.size32,
      ),
      border: Border.all(
        color: Colors.black.withOpacity(0.1),
      ),
      boxShadow: [
        BoxShadow(
          color: Colors.black.withOpacity(0.05),
          blurRadius: 5,
          spreadRadius: 5,
        ),
      ],
    ),
    child: Text( // 컨테이너 안에 들어갈 텍스트
      widget.interest,
      style: TextStyle(
          fontWeight: FontWeight.bold,
          color: _isSelected ? Colors.white : Colors.black87),
    ),
  ),
)

 

Animation 효과 줄 수 있는 위젯 (부분 랜더링 가능) [추천]

class _VideoPostState extends State<VideoPost>
    with SingleTickerProviderStateMixin {

	_animationController = AnimationController(
	  vsync: this,     // 프레임마다 SingleTickerProviderStateMixin의 ticker가 상태를 체크해준다
	  lowerBound: 1.0, // 작아지는 배율
	  upperBound: 1.5, // 커지는 배율
	  value: 1.5,      // 초기 크기 (upperBound를 넘지 못한다)
	  duration: _animationDuration,
	);

	void _onTogglePause() {
    if (_videoPlayerController.value.isPlaying) {
      _animationController.reverse(); // 애니메이션 역진행 만약 lower Bounce 값으로 애니메이션 실행
    } else {
      _animationController.forward(); // 애니메이션 정상진행 만약 upper Bounce 값으로 애니메이션 실행
    }
    setState(() {
      _isPaused = !_isPaused;
    });
  }

	AnimatedBuilder
	  animation: _animationController, // 컨트롤러 매핑을 이용해 부분 렌더링
	  builder: (context, child) {
	    return Transform.scale(
	      scale: _animationController.value,
	      child: child, // 밑에 child값을 실행시킨다
	    );
	  },
	  child: AnimatedOpacity(
	    opacity: _isPaused ? 1 : 0,
	    duration: _animationDuration,
	    child: const FaIcon(
	      FontAwesomeIcons.play,
	      color: Colors.white,
	      size: Sizes.size52,
	    ),
	  ),
	),
}
반응형
반응형

📝정적페이지 생성 (StatelessWidget)

import 'package:flutter/material.dart';

// main method
void main() {
  runApp(App());
}

// Root (whatever you want, you draw it here)
class App extends StatelessWidget {

  // build method hepls your UI render
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      
    );
  }
}

Flutter를 이용해 정적인 화면을 시뮬레이터 화면을 띄우려면 StatelessWidget을 상속받고 build라는 함수를 구현해야한다.

Flutter의 실행과정을 간단히 이야기하자면 main.dart 페이지에서 실행이 되며 runApp을 통해 앱이 실행된다.

 

📝 Custom Widget 생성

const CurrencyCard(
  name: 'Euro',
  code: 'EUR',
  amount: '6 428',
  icon: Icons.euro_rounded,
  isInverted: false,
),

/** -------- Currecy Card ---------  **/

class CurrencyCard extends StatelessWidget {
  final String name, code, amount;
  final IconData icon;
  final bool isInverted;

  final _blackColor = const Color(0xFF1F2123);

  const CurrencyCard({
    super.key,
    required this.name,
    required this.code,
    required this.amount,
    required this.icon,
    required this.isInverted,
  });

  @override
  Widget build(BuildContext context) {
    return Container( ....);
   }
}

생성자를 만들어서 나만의 위젯을 만들어 재활용 할 수 있다.

 

📝정적페이지 생성 (StatefulWidget)

import 'package:flutter/material.dart';

void main() {
  runApp(App());
}

class App extends StatefulWidget {
  const App({super.key});

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  int counter = 0;

  void onClicked() {
    setState(() {
      counter = counter + 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: const Color(0xFFF4EDDB),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                'Click Count',
                style: TextStyle(fontSize: 30),
              ),
              Text(
                '$counter',
                style: const TextStyle(fontSize: 30),
              ),
              IconButton(
                iconSize: 40,
                onPressed: onClicked,
                icon: const Icon(
                  Icons.add_box_rounded,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

데이터가 동적으로 변해야 할 때 쓴다. 예를 들자면 "클릭시 카운트가 올라간다."가 이에 해당한다. 동적페이지를 만들기 위해서는 StatefulWidget을 상속받아 사용해야한다.

 

StatefulWidget을 사용할 때 주의점들은 아래와 같다.

  • 전체가 리렌더링되면 부하가 크기 때문에 부분적으로 따로 위젯을 뽑아 사용한다. (Custom Widget으로 분리)
  • 상태가 변한 걸로 화면이 바뀌었다는 걸 인식하기 때문에 같은 위젯들의 상태가 변화하는 경우 key를 할당해줘야한다. → Custom으로 여러개 만든 경우 그들끼리 구분하기 위해서 필요하다. React이서 Key를 정해주는 것과 같은 개념
  • 데이터 변화가 있는 후 setState를 작동시 state의 변화를 감지해 statefulWidget을 다시 랜더링하게 된다.

 

동작 방식

  1. const App({super.key}); 에서 상위에 위젯 키를 넘긴다
  2. State<App> createState() => _AppState(); → _AppState에서 Build할 내용을 받아와서 상태를 만든다

 

📝Widget Lifecycle (위젯 라이프 사이클)

  1. initState()
    • build를 하기 전에 항상 먼저 실행된다. (대표적으로 API를 불러올 때 사용된다)
  2. dispose()
    • 화면에서 사라질 때 실행한다.
  3. build()
    • 화면을 그려주는 역할을 한다. setState와 같이 리랜더링시 이 부분만 작동하게 된다.

 

 

📝Key란?

대부분의 경우는 key를 사용하지 않는다. 하지만 상태를 유지하고 있는 같은 종류의 위젯을 컬렉션에 더하거나, 제거하거나, 정렬할 때 key가 필요하다. 예를 들면 ToDoList를 보면 List안에 내용들은 똑같은 내용 즉, 똑같은 위젯을 재활용한 것이기 때문에 이것들을 관리하기 위해서는 key가 필요합니다. 이건 동적인 데이터이기 때문에 key가 쓰이는 경우는 StatefulWidget에 해당합니다.

 

자세한 내용은 아래 블로그를 참고하시길 바랍니다.

https://nsinc.tistory.com/214

 

[Flutter] Key란 무엇인가?

기본적으로 플러터의 위젯은 생성자에서 Key매개변수를 받을 수 있습니다. 하지만 그렇게 많이 사용되지는 않습니다. 위젯이 위젯트리에서 위치를 변경하더라도 Key는 상태정보를 유지합니다.

nsinc.tistory.com

 

📝LocalKey vs GlobalKey

Key는 LocalKey와 GlobalKey 두개의 종류가 있습니다.

 

LocalKey

Widget 안에서 사용되는 유니크한 키입니다. 다양한 LocalKey 종류가 있는데 특징은 아래와 같습니다.

 

  • ValueKey
    • 하나의 정보에서 생성되는 키, 숫자, 문자열 등
  • ObjectKey
    • 객체에서 생성하는 키, 같은 타입이라도 객체의 내용이 다르면 다른 키가 된다.
  • UniqueKey
    • 특정 Widget 내에서 고유 한 키
  • PageStorageKey
    • 페이지 스크롤 위치가 있는 키

 

GlobalKey

전체 위젯 트리에서 고유한 전역적인 식별자어디에서나 전역적으로 해당 위젯을 식별하고 액세스할 수 있다.

StatefulWidget을 사용하는 위젯에서 상태를 관리하거나 위젯 트리 외부에서 상태에 액세스해야 할 때 사용됩니다.

 

 

GlobalKey 사용되는 곳

  • 해당 위젯의 상태에 직접 접근가능 (위젯 크기, 값 등…)
final key = GlobalKey<MyWidgetState>();
MyWidget(key: key);
key.currentState.someMethod();
  • Form 위젯에서 여러 FormField 관리할 때 수행 (해당 내용은 Docs에서 가이드한 내용이기 때문에 너무 깊게 생각할 필요는 없음)
final formKey = GlobalKey<FormState>();

if (formKey.currentState.validate()) {
  formKey.currentState.save();
  // ... 폼 저장 처리
}

 

 

📝Build Context

import 'package:flutter/material.dart';

void main() {
  runApp(App());
}

class App extends StatefulWidget {
  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        textTheme: const TextTheme(
          titleLarge: TextStyle(
            color: Colors.red,
          ),
        ),
      ),
      home: const Scaffold(
        backgroundColor: Color(0xFFF4EDDB),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              MyLargeTitle(),
            ],
          ),
        ),
      ),
    );
  }
}

class MyLargeTitle extends StatelessWidget {
  const MyLargeTitle({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      'My Large Title',
      style: TextStyle(
        fontSize: 30,
        color: Theme.of(context).textTheme.titleLarge?.color,
      ),
    );
  }
}

하위 위젯인 MyLargeTitle에서 상위에 있는 위젯을 사용하려면 상위 위젯에 대한 문맥(context)를 알아야하는데 BuildContext그러한 역할을 해준다. 그래서 Theme.of(context)를 이용해 context에 있는 Theme을 가져오고 그 안에 textTheme 그 안에 titleLarge를 가져오면 된다.

 

 

반응형
반응형

📝위젯

Widget이란 앱에 있는 요소를 의미한다 (버튼, 글자, 스크롤 박스 등…)

 

📝위젯 사용 주의점

어떤 위젯이 부모의 크기를 비율로 가져가는 경우 부모의 크기가 이론상 무한대로 확장 가능할 때 제한해야한다 예를 들면 Row가 부모 위젯인 경우 Width가 이론상 무한이고 Column의 경우 Height가 이론상 무한이기 때문에 이 두개가 있을 경우 Expanded따위로 제한해야한다 참고로 Row 안에 Column 안에 있는 위젯의 경우 둘다 Expanded로 제한해줘야한다

 

📝Flutter 개발 팁

// Size 예시 [Gaps, FontSize, Color 등…]

class Gaps {
	static const v1 = SizedBox(height: 1);
	...
}

Size 표가 있으면 통일성이 생긴다

 

📝Flutter 기술

포커스 해제하기 (다른 곳 터치 시 키보드 내리기)

Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _onScaffoldTap,
			...
	)
)

void _onScaffoldTap() {
	FocusManager.instance.primaryFocus?.unfocus(); // 현재 포커스 제거
}

 

다크모드 설정

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tiktok_clone/constants/sizes.dart';
import 'package:tiktok_clone/features/authentication/sign_up_screen.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await SystemChrome.setPreferredOrientations(
    [
      DeviceOrientation.portraitUp,
    ],
  );

  runApp(const TikTokApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'TikTok Clone',
      themeMode: ThemeMode.system, // 시스템에 따라 다르다
      // themeMode: ThemeMode.dark,   // 다크모드 강제
      // themeMode: ThemeMode.light,  // 라이트모드 강제

      theme: ThemeData(
        // 라이트 모드일 때 사용
        brightness: Brightness.light,
        scaffoldBackgroundColor: Colors.white,
        primaryColor: const Color(0xFFE9435A),
        textSelectionTheme: const TextSelectionThemeData(
          cursorColor: Color(0xFFE9435A),
        ),
        splashColor: Colors.transparent,
        appBarTheme: const AppBarTheme(
          foregroundColor: Colors.black,
          backgroundColor: Colors.white,
          elevation: 0,
          titleTextStyle: TextStyle(
            color: Colors.black,
            fontSize: Sizes.size16 + Sizes.size2,
            fontWeight: FontWeight.w600,
          ),
        ),
				tabBarTheme: TabBarTheme( // 탭바에서 사용하는 다크 모드 내용들
          labelColor: Colors.black, // 선택 글자
          unselectedLabelColor: Colors.grey.shade500, // 노 선택 글자
          indicatorColor: Colors.black, // 아래 글자
        ),
      ),
      darkTheme: ThemeData(
        // 다크모드일 때 설정
        brightness: Brightness.dark, // 글자색상??
        scaffoldBackgroundColor: Colors.black, // scafoold 색상
        bottomAppBarTheme: BottomAppBarTheme(
          // 바텀앱바 색상
          color: Colors.grey.shade900,
        ),
        primaryColor: const Color(0xFFE9435A),
      ),
      home: const SignUpScreen(),
    );
  }
}

 

회전 막기

void main() async {
  WidgetsFlutterBinding.ensureInitialized(); // 플러터의 화면이 나오기전에 아래 설정을 사용하겠습니다라는 뜻 

  await SystemChrome.setPreferredOrientations(
    [
      DeviceOrientation.portraitUp, // 핸드폰 회전 막기
    ],
  );
  runApp(const TikTokApp());
}

 

Text Theme

// 방법1 - 직접 Theme 정의하기
theme: ThemeData(
  // 라이트 모드일 때 사용
  brightness: Brightness.light,
  textTheme: TextTheme(
    // <https://m2.material.io/design/typography/the-type-system.html#type-scale> 해당 사이트에서 폰트에 따른 크기를
    // css 또는 flutter전용 textTheme형식을 폰트체만 정해주면 코드를 자동 생성해준다 (Material2)
    // textTheme에서 제공하는 것들 displayLarge(이름만 제공)
    displayLarge: GoogleFonts.openSans(
        fontSize: 95, fontWeight: FontWeight.w300, letterSpacing: -1.5),
    displayMedium: GoogleFonts.openSans(
        fontSize: 59, fontWeight: FontWeight.w300, letterSpacing: -0.5),
    displaySmall:
        GoogleFonts.openSans(fontSize: 48, fontWeight: FontWeight.w400),
    headlineMedium: GoogleFonts.openSans(
        fontSize: 34, fontWeight: FontWeight.w400, letterSpacing: 0.25),
    headlineSmall:
        GoogleFonts.openSans(fontSize: 24, fontWeight: FontWeight.w400),
    titleLarge: GoogleFonts.openSans(
        fontSize: 20, fontWeight: FontWeight.w500, letterSpacing: 0.15),
    titleMedium: GoogleFonts.openSans(
        fontSize: 16, fontWeight: FontWeight.w400, letterSpacing: 0.15),
    titleSmall: GoogleFonts.openSans(
        fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 0.1),
    bodyLarge: GoogleFonts.roboto(
        fontSize: 16, fontWeight: FontWeight.w400, letterSpacing: 0.5),
    bodyMedium: GoogleFonts.roboto(
        fontSize: 14, fontWeight: FontWeight.w400, letterSpacing: 0.25),
    labelLarge: GoogleFonts.roboto(
        fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 1.25),
    bodySmall: GoogleFonts.roboto(
        fontSize: 12, fontWeight: FontWeight.w400, letterSpacing: 0.4),
    labelSmall: GoogleFonts.roboto(
        fontSize: 10, fontWeight: FontWeight.w400, letterSpacing: 1.5),
  ),
	listTileTheme: const ListTileThemeData(
    iconColor: Colors.black,
  ),
  scaffoldBackgroundColor: Colors.white,
  primaryColor: const Color(0xFFE9435A),
  textSelectionTheme: const TextSelectionThemeData(
    cursorColor: Color(0xFFE9435A),
  ),
  splashColor: Colors.transparent,
  appBarTheme: const AppBarTheme(
    foregroundColor: Colors.black,
    backgroundColor: Colors.white,
    elevation: 0,
    titleTextStyle: TextStyle(
      color: Colors.black,
      fontSize: Sizes.size16 + Sizes.size2,
      fontWeight: FontWeight.w600,
    ),
  ),
),

// 방법2 - 만들어져있는 폰트 Theme 가져오기
// GoogleFonts의 itim이라는 폰트체 내용 전체 가져오기
textTheme: GoogleFonts.itimTextTheme(
  ThemeData(brightness: Brightness.dark).textTheme, // 다크모드 테마 가져오기
),

// 상세가 먼저 적용되기 때문에 부분적으로 적용시켜도 된다
Text(
  "Sign up for TikTok",
  style: GoogleFonts.abrilFatface(
    textStyle: const TextStyle(
      fontSize: Sizes.size24,
      fontWeight: FontWeight.w700,
    ),
  ),
)

// theme 데이터 가져오기 예제
style: Theme.of(context)
  .textTheme
  .headlineSmall!
  .copyWith(color: Colors.red), // Theme에서 가져오지만 변형이 필요하면 copyWith을 사용한다
  
  
// 방법3 - Typography 사용 (Flutter 자체 내제된 Font Style)
theme: ThemeData(
        textTheme: Typography.blackMountainView,
			...

darkTheme: ThemeData(
        textTheme: Typography.whiteMountainView, // ?
			...

// 일부 항목 커스텀 가능
static const TextTheme blackMountainView = TextTheme(
    displayLarge: TextStyle(debugLabel: 'blackMountainView displayLarge', fontFamily: 'Roboto', color: Colors.black54, decoration: TextDecoration.none),
    displayMedium: TextStyle(debugLabel: 'blackMountainView displayMedium', fontFamily: 'Roboto', color: Colors.black54, decoration: TextDecoration.none),
    displaySmall: TextStyle(debugLabel: 'blackMountainView displaySmall', fontFamily: 'Roboto', color: Colors.black54, decoration: TextDecoration.none),
		...
) // 예시

 

📝자주쓰는 위젯 코드

// 색상지정 및 투명도
Colors.white.withOpacity(0.5)
Colors.white
Color(0xFF181818)

// 폰트 두께
FontWeight.w800

// 보더형태
BorderRadius.circular(25)

// 위젯 경계 넘어갈시 자름
clipBehavior: Clip.hardEdge

// 좌우 패딩
EdgeInsets.symmetric(horizontal: 20,)

// Column에서 가로 정렬, Row에서 세로 정렬
crossAxisAlignment: CrossAxisAlignment.start
crossAxisAlignment: CrossAxisAlignment.end
crossAxisAlignment: CrossAxisAlignment.spaceBetween

// Column에서 수직 정렬, Row에서 가로 정렬
mainAxisAlignment: MainAxisAlignment.start
mainAxisAlignment: MainAxisAlignment.end
mainAxisAlignment: MainAxisAlignment.spaceBetween

 

 

📝 레이아웃 & UI 위젯

Widget 명 역할 키 : 설명 → 값 사용 예) 활용 예제

Widget명 역할 키 : 설명 → 값 사용 예) 활용 예제
MaterialApp 구글 기본앱같은 디자인에 쓰인다 → 지원 위젯이 많다 [커스텀디자인을 많이 만들기 때문에 해당 위젯 사용] home : 위젯 넣을 곳 (일반적 Scaffold 많이 사용) → Widget MaterialApp(home: Scaffold(..))  
Cupertino 아이폰 기본앱같은 디자인에 쓰인다 → 하위 지원 위젯이 별로 없어서 개발하기 힘듬      
Scaffold 앱은 대부분 상 / 중 / 하로 나누어져있는데 쉽게 구성하기 위한 위젯 appBar : 상단에 넣을 위젯 → PreferredSizeWidget
body
: 중단에 넣을 위젯 -> Widget
bottomNavigationBar : 하단에 넣을 위젯 → Widget
backgroundColor: 배경색 → Color
Scaffold(appBar: Padding(..))  
Padding 패딩을 주는 위젯 padding : 패딩옵션 → EdgeInsetschild : 패딩 안에 들어갈 Widget → Widget    
Color 색을 반환해주는 위젯 색 값 → int Color(0xFF181818)  
EdgeInsets 패딩 옵션 위젯 symmetric → 좌우패딩, 상하패딩 all → 상하좌우 패딩 vertical : 상하 패딩 → doublehorizental : 좌우 패딩 → double
Column Children 안에 항목을 세로로 배치한다 (이론상 무한정 배치 가능하지만 화면 벗어난다고 에러 발생) [width 제한 : 상위 위젯크기 (없으면 화면 크기), height 제한 : 무한 (화면 벗어날시 에러)]- 화면 세로줄 하나를 다 차지한다 (넓이만 없어) children : 세로 박스 안에 들어갈 위젯 리스트 → Widget
crossAxisAlignment : 수직 정렬 옵션 →CrossAxisAlignment
children: [const SizedBox(..)]  
SizedBox 빈 박스 생성 (Not Div) [마진 주는 용도로 자주 쓰인다] height : 높이 → int SizedBox ( height : 80 )  
Row Children 안에 항목을 가로로 배치한다 (이론상 무한정 배치 가능하지만 화면 벗어난다고 에러 발생) [width 제한 : 무한 (화면 벗어날시 에러), height 제한 : 상위 위젯크기 (없으면 화면 크기), ]- 화면 가로줄 하나를 다 차지한다 (높이만 없어) mainAxisAlignment : 수평 정렬 → MainAxisAlignment
children : 가로 박스 안에 들어갈 위젯 리스트 → Widget
Row(children : [ …])  
Text 텍스트 위젯 data[생략 가능(필수)] : 텍스트 → String
style : 텍스트 스타일 → TextStyle
Text( 'Hey, Selena', style: TextStyle( ... ),  
TextStyle 텍스트 스타일 위젯 color : 색상 → Color
fontSize : 글자크기 → int
fontWeight : 폰트 두께 -> FontWeight
TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.w800, ),  
AppBar 상단 레이아웃 (헤더) 위젯 title : 헤더에 적을 내용 → String    
Center 수평 및 수직 가운데 정렬된 상태로 정중앙에 위치한다 child : Center 박스 안에 들어갈 Widget → Widget Center( child: Text(”hello word”))  
SingleChildScrollView 스크롤 바 위젯 child : 스크롤바 안에 들어갈 Widget → Widget    
Container child에 있는 위젯 크기만큼 크기가 정해진다 clipBehavior : 내용물 크기가 커서 넘치는 경우 처리 방식 → Clip
decoration : 컨테이너에 설정할 색상 등 옵션 → Decoration
Container(clipBehavior: Clip.hardEdge, …)  
BoxDecoration (This is not Widget) 색상과 둥근 정도 등을 조정한다 color : 박스 색상 → Color
borderRadius : 박스 테두리 둥근 정도 → BorderRadiusGeometry
BoxDecoration(borderRadius: BorderRadius.circular(25))  
Icon 아이콘 위젯 icon[생략 가능] : 사용할 아이콘 → IconData
color : 아이콘 색상 → Color
size : 아이콘 크기 → int
Icon( icon, color: isInverted ? _blackColor : Colors.white, size: 88, ),  
Transform.scale Child에 적은 위젯의 크기 및 색상 등을 조정한다 scale : 크기 → double
child : Scale 조절할 위젯 → Widget
Transform.scale( scale: 2.2, ... )  
Transform.translate Child에 적은 위젯의 위치를 이동시킨다(scale과 같이 적용시키는 경우 Transform.scale( … child : Transform.translate( .. child : 적용시킬 위젯) 순으로 적용시킨다 offset : 위치 → Offset
child : 회전 시킬 위젯 → Widget
Transform.translate( offset: const Offset(..))  
Offset 현재 위치 x, y축 이동 위젯 dx[생략 가능] : 현재 위치에서 x축 이동 → doubledy[생략 가능] : 현재 위치에서 y축 이동 → double Offset(-5,12)  
IconButton 버튼 이벤트가 있는 아이콘 위젯 icon : 아이콘 종류 → Icon
onPressed : 눌렀을 때 동작 시키기 → Function:void
iconSize : 아이콘 사이즈 → int
onPressed: onClicked  
ThemeData 공통 스타일을 적용시킬 Data를 설정한다 (이름은 이미 정해져있다 textTheme, focusColor 등..) → 결국 그냥 이름일 뿐이고 반환값만 신경쓰고 문맥에 따른 것만 잘 생각해서 정하면 된다 textTheme : 텍스트 테마 → TextTheme textTheme: const TextTheme(...)  
TextTheme 텍스트 스타일 적용시킬 Theme을 정한다 이름은 이미 정해져있다 titleLarge 등..) → 결국 그냥 이름일 뿐이고 반환값만 신경쓰고 문맥에 따른 것만 잘 생각해서 정하면 된다 titleLarge : → TextStyle textTheme: const TextTheme( titleLarge: TextStyle( color: Colors.red, ),  
Flexible Flex박스를 만든다 → 각각의 박스를 비율로 줄 수 있어서 어떤 핸드폰이든 %이기 때문에 깨지지 않는다 flex : 비율 → int
child : FlexBox에 넣을 위젯 → Widget
Flexible( flex: 1,…  
Expanded 사용 가능한 크기만큼 가져갑니다 (최대 화면의 크기) child : 확장된 Row안에 들어갈에 넣을 위젯 → Widget
flex : 나눌 비율 크기 → int
Expanded( child: Container(  
ListView 리스트의 형태로 데이터를 보여준다 → 많은 데이터 보여줄 때 사용 하지만… 모든 데이터를 가져오기 때문에 메모리 누수 현상 발생 가능성 존재      
ListView.builder 리스트 형태로 데이터를 보여주고 화면에 보이는 부분만 렌더링 하기 때문에 메모리 누수 걱정이 없다 scrollDirection : 리스트뷰 스크롤 방향 → ScrollWidget
itemCount : 전체 아이템 수 → int
padding : 패딩 옵션 → EdgeInsets
itemBuilder : 새로운 인덱스 감지시 itemBuilder함수를 호출해 렌더링할 위젯을 설정한다 추후 스크롤 시 안 보이는 부분은 사라지고 나중에 다시 보일 때 재활용하게 된다 → 일반적으로 itemBuilder : (context, index) 이런형식으로 이용된다
   
ListView.seperator ListView.builder와 동일하며 리스트 사이에 위젯을 넣을 수 있다 위와 동일seperatorBuilder : 리스트 사이에 들어갈 위젯 → 일반적으로 speratorBuilder : (BuildContext context, int index) ⇒ 위젯..    
Image.network 이미지를 가져온다      
CircularProgressIndicator 로딩바 X CircularProgressIndicator()  
SafeArea 와이파이, 배터리 등 위에 있는 부분을 제외한 위젯 박스 child : 모든 위젯 [일반적으로 Column, Container 등..] → Widget SafeArea(child: Column...)  
FractionallySizedBox 부모 위젯의 전체 크기를 비율로 가져가는 위젯 박스 widthFactor : 0.5 → double
heightFactor : 0.5 → double
child : 비율로 가져가는 하위 위젯 → Widget
FractionallySizedBox(widthFactor:0.5, heightFactor:0.5, child: Column…)  
Positioned.fill 화면을 꽉채울 수 있는 위젯 child : 화면에 꽉 채울 위젯 → Widget Positioned.fill(child:…)  
Stack 화면 위에 쌓을 수 있는 위젯 (absolute처럼 위젯 위에 올릴 수 있다) children : 쌓을 위젯들 → Widget[] Stack(children: […]) 박스 안에 [아이콘
Positioned 위젯을 상하좌우 값을 줘서 위치시킬 수 있다 left : left 위젯 위치 값 → int
right : right 위젯 위치 값 → int
top : top 위젯 위치 값 → int
bottom : bottom 위젯 위치 값 → int
Positioned(left:20,top:40,child:...)  
Wrap Wrap은 가로로 Children Widget들을 채우는데 더 이상 못 채우면 알아서 줄바꿈이 된다 runSpacing : 줄 간격 → int
spacing : 요소들 사이 가로 간격 → int
children : 적용시킬 위젯들 → Widget[]
Wrap(runSpacing: 15, spacing: 15, children: ...)  
TextField 인풋박스를 제공해준다. 유효성 검사 등 다양한 기능 제공     https://api.flutter.dev/flutter/material/TextField-class.html
TabBar 앱 하단에 3개나 5개 아이콘 등에 쓰이는 Tab들을 의미한다.     https://api.flutter.dev/flutter/material/TabBar-class.html
TabPageSelector 슬라이드해서 페이지를 넘어가는 탭들의 경우 현재 어떤 탭 위치에 있는지 알려주는 역할을 한다.     https://api.flutter.dev/flutter/material/TabPageSelector-class.html
InputDecoration TextField 또는 TextFormField와 같은 입력 필드를 꾸미는데 사용되는 위젯입니다.     https://api.flutter.dev/flutter/material/InputDecoration-class.html
UnderlineInputBorder TextField 또는 TextFormField의 밑줄 스타일로 설정할 때 사용됩니다.     https://api.flutter.dev/flutter/material/UnderlineInputBorder-class.html
BorderSide 경계선(border)의 색상, 두께 및 스타일을 정의하는 위젯입니다.     https://api.flutter.dev/flutter/painting/BorderSide-class.html
TabBarView 여러 페이지를 탭 전환을 통해 스와이프할 수 있는 뷰를 제공.     https://api.flutter.dev/flutter/material/TabBarView-class.html
AspectRatio 자식 위젯의 너비와 높이 비율을 일정하게 유지시켜주는 위젯입니다     https://api.flutter.dev/flutter/widgets/AspectRatio-class.html
TextFormField TextField와 비슷하지만 Form 위젯과 함께 사용됩니다.     https://api.flutter.dev/flutter/material/TextFormField-class.html
NavigationDestination 네비게이션에 보여줄 항목을 정의해주는 위젯입니다.     https://api.flutter.dev/flutter/material/NavigationDestination-class.html
PageView 수평 또는 수직으로 페이지를 스와이프할 수 있게 해주는 위젯입니다.     https://api.flutter.dev/flutter/widgets/PageView-class.html
FadeInImage.assetNetwork 이미지가 로드되는 동안 보여줄 Placeholder 이미지입니다. (로딩개념)     https://api.flutter.dev/flutter/widgets/FadeInImage/FadeInImage.assetNetwork.html
NavigationBar 3.0에서 새롭게 도입된 신개념으로 BottomNavigationBar보다 선호된다. TabBar의 경우 하단이 아니여도 붙일 수 있지만 NavigationBar의 경우는 하단에 붙는다.     https://api.flutter.dev/flutter/material/NavigationBar-class.html
ListTile List형식인데 알림으로 오는 형식처럼 앞뒤에 붙여서 리스트를 쉽게 만들수 있게 도와준다.     https://api.flutter.dev/flutter/material/ListTile-class.html
Dismissible List형식인데 왼쪽이나 오른쪽 슬라이드로 삭제하거나 할 수 있는 기능이다.     https://api.flutter.dev/flutter/widgets/Dismissible-class.html
GoRoute 라우팅, 중첩라우팅기능, 애니메이션전환 등을 제공한다.     https://pub.dev/packages/go_router
VerticalDivider 위젯들 사이에 수직선을 넣어 구분할 수 있는 위젯     https://api.flutter.dev/flutter/material/VerticalDivider-class.html
RichText 위젯을 배열로 가지기 때문에 TextSpan위젯을 사용시 첫글자는 다른 스타일 중간 글자는 다른 스타일을 넣기가 편하다     https://api.flutter.dev/flutter/widgets/RichText-class.html
DefaultTextStyle 텍스트 위젯을 여러 개 써야할 때 최상단 스타일 고정하고 여러 항목을 받을 수 있다.     https://api.flutter.dev/flutter/widgets/DefaultTextStyle-class.html
NestedScrollView 상단의 헤더 영역과 본문 영역이 서로 독립적인 스크롤을 처리하면서도, 상호작용하는 방식으로 동작하는 게 특징입니다. 예를 들어, 상단의 헤더가 먼저 스크롤되고, 그 이후에 본문 영역이 스크롤되는 방식입니다.     https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html

 

 

모달창

// 모달창 (Yes / No) iOS
onTap: () {
  showCupertinoDialog(
    context: context,
    builder: (context) => CupertinoAlertDialog(
      // iOS 모달창
      title: const Text("Are you sure?"),
      content: const Text("Plx dont go"),
      actions: [
        CupertinoDialogAction(
          // iOS 모달 No 버튼
          onPressed: () => Navigator.of(context).pop(),
          child: const Text("No"),
        ),
        CupertinoDialogAction(
          // iOS 모달 Yes 버튼
          onPressed: () => Navigator.of(context).pop(),
          isDestructiveAction: true,
          child: const Text("Yes"),
        ),
      ],
    ),
  );
},

// 모달창 (Yes / No) AOS
onTap: () {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      // AOS 모달창
      icon: const FaIcon(FontAwesomeIcons.skull),
      title: const Text("Are you sure?"), // 제목 내용
      content: const Text("Plx dont go"), // 제목 안에 내용
      actions: [
        IconButton(
          onPressed: () => Navigator.of(context).pop(),
          icon: const FaIcon(FontAwesomeIcons.car),
        ), // 왼쪽
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: const Text("Yes"),
        ), // 오른쪽
      ],
    ),
  );
}

// 모달창 하단형 (iOS)
onTap: () {
	showCupertinoModalPopup(
	  // 모달창 하단형
	  context: context,
	  builder: (context) => CupertinoActionSheet(
	    // 모달창 하단형에 들어갈 내용
	    title: const Text("Are you sure?"),
	    message: const Text("Please dooooont gooooo"),
	    actions: [
	      CupertinoActionSheetAction(
	        isDefaultAction: true, // OK (파란글씨)
	        onPressed: () => Navigator.of(context).pop(),
	        child: const Text("Not log out"),
	      ),
	      CupertinoActionSheetAction(
	        isDestructiveAction: true, // 취소 (빨간 글씨)
	        onPressed: () => Navigator.of(context).pop(),
	        child: const Text("Yes plz."), // 들어갈 문구
	      )
	    ],
	  ),
	);
},

 

스크롤 (sliver)

import 'package:flutter/material.dart';

class UserProfileScreen extends StatefulWidget {
  const UserProfileScreen({super.key});

  @override
  State createState() => _UserProfileScreenState();
}

class _UserProfileScreenState extends State {
  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [ // 앱의 스크롤에따라 모양이 변하는 것들을 말한다
	      // <https://api.flutter.dev/flutter/material/SliverAppBar-class.html> 샌드박스 화면으로 보면 이해가 쉽다.
        SliverAppBar(
					snap:
              true, // floating가 ture일 때 동작하며 SliverFixedExtentList에 리스트들을 살짝 위로 스크롤하면 서서히 나오는게 아니라 한번에 짠 하고 나온다
          floating:
              true, // SliverFixedExtentList에 있는 리스트를  위로 스크롤할 때 올릴 때 해당 앱바가 출현한다(끝까지 올리지 않는 이상 안 나옴 일반적)
          stretch: true, // 해당 앱바를 아래로 당길 수 있냐 (그래서 늘어나냐 안 늘어나냐)
          pinned: true, // 내렸을 때 앱바를 고정시킬지 여부
          backgroundColor: Colors.teal, // 위로 당겼을 때 보여줄 것
          collapsedHeight: 80, // 높이 (언제부터 위에 bacground color teal을 실행시킬거냐에 대한)
          expandedHeight: 200
          flexibleSpace: FlexibleSpaceBar(
            stretchModes: const [
              StretchMode.blurBackground, // -> 아래로 당길시 뿌얘짐
              StretchMode.zoomBackground, // 밑에 선언한 백그라운드를 중심으로 당겨진다
              StretchMode.fadeTitle, // Title이 뿌얘진다
            ],
            background: Image.asset(
              "assets/images/placeholder.jpg",
              fit: BoxFit.cover,
            ),
            title: const Text("Hello!"),
          ),
        ),
        // 스크롤이 가능한 위젯 생성
				const SliverToBoxAdapter( 
          child: Column(
            children: [
              CircleAvatar(
                backgroundColor: Colors.red,
                radius: 20,
              ),
            ],
          ),
        ),
        // 스크롤이 가능한 리스트 위젯 생성
				SliverFixedExtentList(
          // Body값 (List)
          delegate: SliverChildBuilderDelegate(
            childCount: 50,
            (context, index) => Container(
              color: Colors.amber[100 * (index % 9)],
              child: Align(
                alignment: Alignment.center,
                child: Text("Item $index"),
              ),
            ),
          ),
          itemExtent: 100, // 높이 차이
        ),
				// SliverAppBar에 비해 사용자 정의가 더 자유롭다.
				SliverPersistentHeader(
          delegate: CustomDelegate(),
          pinned: true,
          floating: true,
        ),
        // 스크롤이 가능한 그리드 위젯 생성
				SliverGrid( 
          delegate: SliverChildBuilderDelegate(
            childCount: 50,
            (context, index) => Container(
              color: Colors.blue[100 * (index % 9)],
              child: Align(
                alignment: Alignment.center,
                child: Text("Item $index"),
              ),
            ),
          ),
          gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 50, // 정사각형 크기
            mainAxisSpacing: Sizes.size20, // 세로 마진
            crossAxisSpacing: Sizes.size20, // 가로 마진
            childAspectRatio: 1, // 정사각형들 비율
          ),
        )
      ],
    );
  }
}

// SliverPersistentHeader에 사용하기 위해 따로 정의 필요
class CustomDelegate extends SliverPersistentHeaderDelegate {
  @override
  Widget build(
    BuildContext context,
    double shrinkOffset,
    bool overlapsContent,
  ) {
    return Container(
      color: Colors.indigo,
      child: const FractionallySizedBox(
        heightFactor: 1, // 차지하는 높이 퍼센티지
        child: Center(
          child: Text(
            'Title!!!!!',
            style: TextStyle(
              color: Colors.white,
            ),
          ),
        ),
      ),
    );
  }

  @override
  double get maxExtent => 150; // 높이

  @override
  double get minExtent => 120; // pin사용시 유지되는 높이 영역

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return false; // 필요 시 다시 빌드 여부
  }
}

Sliver는 Scroll안에 일반적으로 사용되며 스크롤함에 따라 보여지는 게 다른 걸 의미한다.

 

에러 위젯

void showFirebaseErrorSnack(
  // 하단에 에러 메세지 알림 처럼 튀어나옴
  BuildContext context,
  Object? error,
) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      showCloseIcon: true, // 닫기 버튼
      content: Text( // 보여줄 메시지
        (error as FirebaseException).message ?? "Something wen't wrong.",
      ),
    ),
  );
}

 

 

📝그 외 위젯

Widget 명 역할 키 : 설명 → 값 사용 예) 활용 예제

Widget 명 역할 키 : 설명 → 값 사용 예) 활용 예제
FutureBuilder Http통신 후에 데이터 정보를 담는 위젯 future : Future타입의 데이터 → Future<T>
builder : ****future에서 받은 데이터 정보 → AsyncWidgetBuilder -> 일반적으로 (context, snapshot) 사용
   
GestureDetector 동작 이벤트를 제공해준다 onTap : 탭 눌렀을 때 → funciton
onPanUpdate : 드래깅 감지 → function(DragUpdateDetails details)
onPanEnd : 드래깅 끝냈을 때 → function(DragEndDetails details)
- onPageUpdatedetails.delta.dx > 0 → 오른쪽에서 왼쪽으로 스왑  
Navigator.push 데이터를 다른 위젯으로 보내고 Router로 해당 위젯을 띄운다 (뒤로가기가 쌓인다) 일반적으로 **(context, Router)**받고 위젯을 Router 객체로 변환하기 위해서MaterialPageRoute를 사용한다 Navigator.of(context).push(MaterialPageRoute(builder: (context) => const LoginScreen(),),);  
Navigator.pop 뒤로가기   Navigator.*of*(context).pop()  
MaterialPageRoute 위젯을 Router형태로 바꾸는 역할 builder : 어떤 context를 읽을 지 → buildContext
fullscreenDialog : 페이지 이동 효과 → boolean
MaterialPageRoute( builder: (context) => DetailScreen( title: title, thumb: thumb, id: id, ) → [DetailScreen 위젯을 Router로]  

 

 

📝애니메이션 위젯

Widget 명 역할 키 : 설명 → 값 사용 예) 활용 예제

Widget 명 역할 키 : 설명 → 값 사용 예) 활용 예제
Hero 페이지 Route 애니메이션 효과 (히어로 처럼 슝 나옴) 필요하면 알아서 사용하세요   https://docs.flutter.dev/ui/animations/hero-animations
AnimatedOpacity 불투명 애니메이션 위젯 opacity : 불투명도 → double (0~1)
duration : 불투명도 애니메이션 적용 시간 → Duration
child : 적용시킬 위젯 → Widget
AnimatedOpacity(opacity: 1,duration: const Duration(milliseconds: 300), child: …)  
AnimatedCrossFade FadeIn FadeOut 전환 애니메이션 위젯 firstChild : 위젯 → Widget
secondChild : 위젯 → Widget
duration : 애니메이션 적용 시간 → Duration
crossFadeState : 보여줄 위젯 → CrossFadeState.showFirst, CrossFadeState.showSecond
AnimatedCrossFade(fistChild:…, secondChild:…, duration:…, crossFadeState: …)  
AnimatedBuilder 상태 변화를 감지해 화면을 업데이트할 수 있습니다. (컨트롤러로 로직처리를 하며 Builder를 통해 그려줍니다.     https://api.flutter.dev/flutter/widgets/AnimatedBuilder-class.html
AnimatedDefaultTextStyle 텍스트에 애니메이션을 줄수 있는 위젯입니다.     https://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle-class.html
SizeTransition 위젯의 크기(가로 또는 세로 방향)를 애니메이션을 통해 변환하는 위젯입니다.     https://api.flutter.dev/flutter/widgets/SizeTransition-class.html
SlideTransition 슬라이드하는 애니메이션을 적용한 위젯입니다.     https://api.flutter.dev/flutter/widgets/SlideTransition-class.html
AnimatedModalBarrier 모달로서 화면을 가리는 반투명한 배경(Barrier) 위젯을 만듭니다.     https://api.flutter.dev/flutter/widgets/AnimatedModalBarrier-class.html
AnimatedList 리스트의 항목들이 동적으로 추가되거나 제거될 때 애니메이션을 적용할 수 있는 위젯     https://api.flutter.dev/flutter/widgets/AnimatedList-class.html
ColorTween 애니메이션 전환시 색상의 변화되는 과정을 애니메이션으로 보여줍니다.     https://api.flutter.dev/flutter/animation/ColorTween-class.html
Tween 시작과 종료값을 줄 수 있는 애니메이션기능을 줍니다.     https://api.flutter.dev/flutter/animation/Tween-class.html

 

📝Flutter 개발 팁

 

 

 

 

반응형
반응형

📝IDE 설정

저장할 때 마다 trailing comma 바로 코드 적용 (auto prettier)

Format code on save를 통해 저장 시 Prettier가 바로 동작해 정리해준다.

 

 

trailing comma에 대한 Weak Warning Message 표시 및 출력

linter:
  rules:
    require_trailing_commas: true

analysis_options.yaml 파일에 require_trailing_commas: true를 통해 해당 기능을 사용할 수 있다

 

 

📝 안드로이드 스튜디오 Flutter 팁

Flutter Widget Method 또는 Widget으로 Extract

  • Extract Method
    • 메소드로 추출
  • Extract Flutter Widget
    • Widget으로 추출

 

코드 내 Error 및 다양한 메시지 출력하기

  • Inspection Lens Plugin 설치
    • 물론 다른 Error 출력 플러그인을 설치해도 된다

 

Flutter Outline

위젯 구조를 한눈에 알 수 있다.

 

 

Flutter Inspector

위젯과 레이아웃을 한 눈에 볼 수 있다.

 

  • Widget Tree
    • 작성한 코드의 Widget 구조 파악
  • Widget Details Tree
    • 선택한 위젯에 해당하는 상위 하위 구조 및 정밀 옵션 제공
  • Layout Explorer
    • 선택한 위젯에 해당하는 상위 하위 구조를 레이아웃 박스로 시각 자료 제공

 

초록색 동그라미를 눌러 에뮬레이터의 레이아웃을 정밀하게 볼 수 있습니다.

 

하위 위젯 한번에 감싸기 (Wrap with Widget)

Widget이 엄청 많아지면 상위 Widget으로 감싸려고 하는게 매우 힘들다 IDE에서 Widget을 클릭해놓으면 전구(Code Acation)이 나오는데 여기에서 원하는 걸 선택하면 된다

반응형
반응형

📝Flutter 라이브러리

Http

  • Http
    • Flutter에서 가장 기본적으로 많이 사용되는 HTTP 통신 라이브러리입니다. 간단한 GET, POST 요청을 할 때 주로 사용됩니다.
  • DIO (채택)
    • 복잡한 HTTP 요청이나 응답, 예를 들어 인터셉터, 쿠키 관리, 파일 업로드 및 다운로드 등을 쉽게 처리할 수 있는 강력한 HTTP 클라이언트 라이브러리입니다.

 

Logging

  • Flutter는 프론트이기 때문에 프론트의 경우 따로 로깅을 하지 않는다. 로깅이 필요하면 지원하는 logging 라이브러리를 사용한다. 
  • https://pub.dev/packages/logging

 

상태관리

  • Provider
  • Riverpod (Provider 개선버전) [추천]
  • Bloc [추천이라는데 안 써봐서 모름]
  • GetX [추천이라는데 안 써봐서 모름]
  • MobX
  • Redux
  • Cubit

 

그 외 라이브러리

  • Freezed
    • Freezed에 맞게 코드를 짜고 코드제너레이터를 하면 그 필드에 맞게 생성자나, 직렬화, 역직렬화등에 대한 정보를 만들어줍니다. (보일러플레이트 감소)
  • jsonDecode
    • String으로 온 데이터를 Json으로 변환시켜준다. (DIO하고 Freezed 같이 사용하면 따로 필요 없어보인다.)
  • url_launcher
    • launchUrlString을 통해 페이지 이동이 가능하다.
  • SharedPreferences
    • 사용자 내부 디바이스에 저장 및 활용할 수 있게 도와준다.
  • GoRouter
    • 라우팅을 도와줍니다.
  • permission_handler
    • 권한 요청을 간단하게 해주는 것
  • camera
    • 카메라 사용
  • gallery_saver
    • 갤러리 사용

 

📝IDE 설정 & 안드로이드 스튜디오 Flutter 팁

https://mondaymonday2.tistory.com/1007

 

[Flutter] Flutter 팁 (Save시 Prettier 적용, Flutter Widget Method, Widget Extract, Error 메시지, Widget or Method Hierarc

📝IDE 설정저장할 때 마다 trailing comma 바로 코드 적용 (auto prettier)Format code on save를 통해 저장 시 Prettier가 바로 동작해 정리해준다.  trailing comma에 대한 Weak Warning Message 표시 및 출력linter: rules: re

mondaymonday2.tistory.com

 

 

📝네이밍 컨벤션 & Tip

SnakeCase

  • file_system.dart
  • folder
  • import 'package:angular_components/angular_components.dart' as angular_components;

 

CamelCase

  • class SliderMenu { ... }
  • function
  • function args
  • variable

 

PascalCase

  • typedef Predicate<T> = bool Function(T value);
  • @Foo(anArg)
  • extension MyFancyList<T> on List<T> { ... }

 

Tip

  • 타입캐스팅
    • var map = table.asMap(); 또는 toMap이긴한데 얕은복사냐 깊은복사냐에 따라 다르다고하다 그냥 to로 쓰자 귀찮다
  • bool 변수는 긍정이름 선호
    • hasData (O)
    • isEmpty (X)
  • 메소드명
    • 행위가 강조되는 경우 downloadData처럼 데이터를 반환하는 경우 get 제거
    • breakfastOrder (O)
    • getBreakfastOrder (X) 
  • 메소드 매개변수
    • add(element) [O]
    • addElement(element) [X]
  • 함수 New 타입 
    • 클래스를 하나 만들지말고 typedef Predicate<E> = bool Function(E element); 이렇게 써라. 너무 남발하면 복잡해지니 자제하면서 알아서 사용
// 좋은 예시
typedef Comparison<T> = int Function(T a, T b);

// 나쁜 예시
typedef int Comparison<T>(T a, T b); // 구식 문법
  • 메서드 체이닝
    • 체이닝을 위해 return에 this를 반환하는것보다는 매서드 케서케이드(..)를 사용하는게 좋다.
// wrong code

class Person {
  String name = '';
  int age = 0;

  Person setName(String name) {
    this.name = name;
    return this; // `this`를 반환
  }

  Person setAge(int age) {
    this.age = age;
    return this; // `this`를 반환
  }
}

void main() {
  var person = Person()
      .setName('Alice')  // 메서드 체이닝
      .setAge(30);       // 메서드 체이닝
  print('Name: ${person.name}, Age: ${person.age}');
}

// right code
class Person {
  String name = '';
  int age = 0;

  void setName(String name) {
    this.name = name;
  }

  void setAge(int age) {
    this.age = age;
  }
}

void main() {
  var person = Person()
    ..setName('Alice')  // 메서드 캐스케이드
    ..setAge(30);       // 메서드 캐스케이드
  print('Name: ${person.name}, Age: ${person.age}');
}
  • 타입 명시화
    • 로컬 변수 타입이 명확하면 생략하는게 좋습니다.
    • Dart 대부분 제네릭 타입인자 추론이 가능하지만 정보가 없으면 타입인자 명시 해야합니다.
// 좋은 예시
var desserts = <List<Ingredient>>[];

// 나쁜 예시
List<List<Ingredient>> desserts = <List<Ingredient>>[];

// ─────────────────────────────────────────────────────────────

// 좋은 예시
var playerScores = <String, int>{}; // 명시적으로 타입 인자 지정
final events = StreamController<Event>();

// 나쁜 예시
var playerScores = {}; // 타입 인자 없음
final events = StreamController(); // 타입 인자 없음
  • 생성자 생성
// 간단한 방식
Point(this.x, this.y);
  • Setter 반환타입 지정하지 않기
// 좋은 예시
set foo(Foo value) { ... }

// 나쁜 예시
void set foo(Foo value) { ... } // 불필요한 반환 타입
  • dynamic 타입 금지
    • 컴파일 타입체크를 비활성하기 때문에 사용하지 않는 게 좋다
  • 함수 매개변수 명시화
    • 매개변수 위치에 따라 받는 것보다는 이렇게 명시해서 데이터를 직접 받는게 안전하고 이해가 쉽다.
class ListBox {
  final bool scroll;
  final bool showScrollbars;

  ListBox({this.scroll = false, this.showScrollbars = false});
}

void main() {
  // 명명된 매개변수를 사용하여 `ListBox` 인스턴스를 생성
  var listBox = ListBox(scroll: true, showScrollbars: true);
}

// 명명된 매개변수를 가진 함수
void createUser({required String name, int age = 18, String? email}) {
  print('Name: $name, Age: $age, Email: $email');
}
  • == 오버라이딩
    • 기본적으로 ==는 참조값 비교하기 때문에 실제값 비교를 위해서는 오버라이딩이 필요하다

 

 

https://dart.dev/effective-dart/style

https://dart.dev/effective-dart/usage

https://dart.dev/effective-dart/design

 

 

📝Flutter 프로젝트 구조

lib/
 ├─ model
     ├─ my_menu
         ├─ my_menu_model.dart
 ├─ repository
     ├─ my_menu
         ├─ my_menu_repository.dart
 ├─ view_model
     ├─ my_menu
         ├─ my_menu_view_model.dart
 ├─ screen
     ├─ my_menu
         ├─ my_menu_screen.dart
 ├─ common
     ├─ enum
	 ├─ exception
     ├─ logger
     ├─ util
 ├─ widget
     ├─ my_menu
          ├─ profile.dart

다양한 구조가 있지만 MVVM 구조가 앱개발할 때 가장 적합한 스타일이라고한다. (iOS / AOS에도 적극 채용중) Rivderpod을 선택했고 Riverpod의 경우 MVVM에 맞게 개발되었기 때문에 MVVM 프로젝트 구조가 적합하다.

 

  • model
    • 데이터의 모델을 의미한다.
  • repository
    • 비즈니스 로직이 들어간다.
  • view_model
    • 화면과 연결하는 부분으로 API통신해서 가져온 데이터를 가공해서 화면에 전달해주는 브릿지 역할을 합니다.
  • screen
    • 사용자가 보는 화면을 의미합니다. view_model에서는 API통신 후 온 데이터에 대한 가공등이 들어가지만 UI관련 로직은 screen에 들어가게 됩니다.
  • common
    • 공통으로 처리하는 부분입니다.
  • widget
    • 화면의 요소를 컴포넌트화 시킨 것입니다.

 

 

 

반응형
반응형

📝Flutter

dart라는 구글에서 개발한 언어로 크로스 플랫폼이 가능한 강력한 프레임워크 Android, iOS, Web 등 하나의 언어로 개발이 가능하다 C, C++ 로 구현된 엔진을 사용한다

 

📝Flutter 장점

  • 다트(Dart) 언어 사용
    • Flutter는 Google에서 개발한 Dart 언어를 사용합니다. Dart는 JIT(Just-In-Time) 및 AOT(Ahead-Of-Time) 컴파일러를 지원하여 빠른 개발 및 성능 최적화를 제공합니다.
  • NullSafety
    • 애플리케이션 실행 중 null 참조 에러가 많이 발생합니다. null safety는 이 문제를 코드가 실행되기 전 컴파일러가 해당 버그를 잡아줌으로써 예상치 못한 상황을 대비할 수 있게 해줍니다. null을 없애자는 게 아닙니다.
  • 위젯 기반 아키텍처
    • UI요소를 위젯으로 정의하는데 위젯은 독립적이고 재사용이 가능합니다.
  • 크로스 플랫폼
    • Flutter는 하나의 코드베이스로 iOS, Android, Web, Desktop App 개발이 모두 가능합니다.

 

📝Flutter 단점

  • 불완전한 크로스플랫폼
    • 앱에는 좋지만 데스크탑이나 웹에는 좀 아쉽습니다.
  • 구글
    • 구글은 진행중인 프로젝트를 가져다 버리는 경우가 허다합니다. 매력적인 프레임워크지만 버려질 가능성도 있다는 문제점이 있습니다.
  • 성능
    • 네이티브보다 성능이 떨어지긴한다. 얼마나 차이있는지는 비교를 못해서 솔직히 잘 모르겠습니다.

 

📝Flutter 아키텍처

Framework

Framework는 사용자가 직접 개발하는 부분으로 이 계층은 주로 UI와 관련된 로직과 기능을 제공합니다.

  • Material
    • 구글의 머티리얼 디자인 가이드라인에 따른 UI 구성 요소들을 제공합니다.
  • Cupertino
    • 애플의 iOS 스타일의 UI 구성 요소들을 제공합니다.
  • Widgets
    • Flutter 애플리케이션의 기본 빌딩 블록입니다. 모든 UI 구성 요소들은 위젯으로 표현됩니다.
  • Rendering
    • 위젯을 렌더링 트리로 변환하여 화면에 그릴 수 있는 레이아웃을 계산합니다.
  • Animation
    • 애니메이션을 위한 기능들을 제공합니다. 시간에 따라 위젯의 상태를 변경하는 등의 작업을 담당합니다.
  • Painting
    • 화면에 직접 그리기 위한 저수준의 도구들을 제공합니다.
  • Gestures
    • 사용자의 입력 제스처(탭, 스와이프 등)를 처리하는 기능을 제공합니다.
  • Foundation
    • Flutter의 기본 기능들을 제공하며, 다른 모든 상위 모듈들이 이를 기반으로 동작합니다.

Engine

C++로 작성된 Flutter 엔진으로 다양한 플랫폼에서 UI를 동일하게 렌더링하기 위해 필수적인 기능을 제공합니다.

  • Service Protocol
    • 개발 중 디버깅과 관련된 서비스들을 관리합니다.
  • Composition
    • 여러 레이어의 화면을 결합하여 최종 렌더링 결과를 만듭니다.
  • Platform Channels
    • Flutter와 네이티브 코드(Android, iOS 등) 간의 통신을 처리합니다.
  • Dart Isolate Setup
    • Dart의 비동기 작업을 관리하는 Isolate의 설정을 처리합니다.
  • Rendering
    • 위젯을 실제로 화면에 그리기 위해 필요한 모든 렌더링 작업을 수행합니다.
  • System Events
    • 시스템 이벤트(예: 네트워크 상태 변경 등)를 처리합니다.
  • Dart Runtime Management
    • Dart 런타임을 관리하여 앱이 올바르게 동작하도록 합니다.
  • Frame Scheduling
    • 애플리케이션의 프레임 렌더링 스케줄을 관리합니다.
  • Asset Resolution
    • 이미지나 폰트 같은 애셋을 로드하고 적절한 해상도로 변환합니다.
  • Frame Pipelining
    • 프레임을 렌더링할 때 효율적으로 처리되도록 파이프라인을 구성합니다.
  • Text Layout
    • 텍스트를 레이아웃하고 렌더링합니다.

Embedder

플랫폼에 맞게 Flutter 애플리케이션을 통합하고 실행할 수 있게 하는 역할을 합니다.

  • Render Surface Setup
    • 렌더링을 위해 필요한 표면을 설정합니다. 각 플랫폼마다 다를 수 있습니다.
  • Native Plugins
    • 플랫폼 별로 네이티브 기능(카메라, GPS 등)에 접근할 수 있게 해주는 플러그인을 관리합니다.
  • App Packaging
    • 앱을 패키징하여 배포 가능한 형태로 만듭니다.
  • Thread Setup
    • 앱이 실행되는 스레드를 설정합니다.
  • Event Loop Interop
    • 이벤트 루프와 네이티브 이벤트를 통합하여 상호 작용할 수 있게 합니다.

 

📝Flutter 아키텍처 (다른 관점)

 

  • Dart App
    • Dart로 작성된 애플리케이션 코드.
  • Framework
    • Flutter 기반 위젯과 API를 제공하는 프레임워크.
  • Engine
    • Dart 코드를 실행하고 UI를 렌더링하는 역할.
  • Embedder
    • 네이티브 시스템과 Flutter 엔진 간의 통신을 관리.
  • Runner
    • 각 플랫폼(iOS, Android)에서 앱을 실행할 수 있게 하는 환경을 제공.

 

📝 플러터 빌드 내용

Dart언어, Flutter 엔진, 그래픽디자인툴이 들어간 녀석을 Android 또는 iOS와 연결시켜 빌드 파일을 만든다. 빌드 파일안에는 Dart언어, Flutter 엔진, 그래픽디자인툴 등이 들어가있다.

 

 

🔗 참고 및 출처

https://medium.com/flutter-korea/flutter%EC%9D%98-null-safety-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-dd4ee1f7d6a5

 

 

반응형