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