반응형

📝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,
	    ),
	  ),
	),
}
반응형