참고 영상: (https://www.youtube.com/watch?v=qKIgy26u-uE&t=6s)
참고영상을 바탕으로 provider 방식을 Getx 방식으로 변경한 코딩입니다.
- 전체 흐름도 (이해를 돕기위해서 코딩을 의역한 부분이 있습니다)
구성 : main.dart, app.dart, movie.dart, movie_repository.dart, movie_controller.dart, category_movie_list.dart
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_tmdb/src/app.dart';
import 'package:flutter_tmdb/src/controller/movie_controller.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: App(),
);
}
}
app.dart(view)
import 'package:flutter/material.dart';
import 'package:flutter_tmdb/src/components/category_movie_list.dart';
import 'package:flutter_tmdb/src/controller/movie_controller.dart';
import 'package:get/get.dart';
class App extends StatefulWidget {
@override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
final MovieController _movieController = Get.put(MovieController());
@override
void initState() {
super.initState();
}
Widget _genreTag(Map<String, dynamic> genre) {
var isActive = _movieController.activeGenreId.value == genre['id'];
return GestureDetector(
onTap: () {
_movieController.changeCategory(genre);
},
child: Container(
padding: const EdgeInsets.all(10),
margin: const EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
borderRadius: BorderRadius.all(Radius.circular(30)),
color: isActive ? Colors.grey : Colors.white,
),
child: Text(
genre['name'],
style: TextStyle(
color: isActive ? Colors.white : Colors.grey, //
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Movie Catergory'),
),
body: Column(
children: <Widget>[
FutureBuilder(
future: _movieController.loadGenre(),
builder: (BuildContext context,
AsyncSnapshot<List<Map<String, dynamic>>> snapshot) {
if (snapshot.hasData) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Obx(()=>Row(
children: List.generate(snapshot.data!.length,
(index) => _genreTag(snapshot.data![index])),
)),
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}),
CategoryMovieList(), // => 장르별 영화정보를 표시하는 부분
],
),
);
}
}
movide.dart(model)
class Movie {
Movie({
required this.adult,
required this.backdropPath,
required this.genreIds,
required this.id,
required this.originalLanguage,
required this.originalTitle,
required this.overview,
required this.popularity,
required this.posterPath,
required this.releaseDate,
required this.title,
required this.video,
required this.voteAverage,
required this.voteCount,
});
bool adult;
String backdropPath;
List<int> genreIds;
int id;
String originalLanguage;
String originalTitle;
String overview;
double popularity;
String posterPath;
DateTime? releaseDate;
String title;
bool video;
double voteAverage;
int voteCount;
factory Movie.fromJson(Map<String, dynamic> json) => Movie(
adult: json["adult"],
backdropPath: json["backdrop_path"] ?? '',
genreIds: List<int>.from(json["genre_ids"].map((x) => x)),
id: json["id"],
originalLanguage: json["original_language"],
originalTitle: json["original_title"],
overview: json["overview"],
popularity: json["popularity"].toDouble(),
posterPath: json["poster_path"],
releaseDate: json["release_date"] != null
? DateTime.parse(json["release_date"])
: null,
title: json["title"],
video: json["video"],
voteAverage: json["vote_average"].toDouble(),
voteCount: json["vote_count"],
);
String get posterUrl => "https://image.tmdb.org/t/p/w500/$posterPath";
}
movie_controller.dart(controller)
import 'package:flutter_tmdb/src/models/movie.dart';
import 'package:flutter_tmdb/src/repository/movie_repository.dart';
import 'package:get/get.dart';
class MovieController extends GetxController {
static MovieController get to => Get.find();
var _movieRepository = MovieRepository();
RxList<Movie> movies = <Movie>[].obs;
RxInt activeGenreId = (-1).obs;
Future<List<Map<String, dynamic>>> loadGenre() async {
var genreList = await _movieRepository.loadGenre();
print('genreList: ${genreList.toString()}');
if (genreList.isNotEmpty) {
activeGenreId.value = genreList.first['id'].toInt();
_loadMovieListWithGenre();
}
return genreList;
}
void _loadMovieListWithGenre() async {
// await Future.delayed(Duration(seconds: 2));
movies.value = await _movieRepository.loadMovieListWithGenre(activeGenreId.value);
}
void changeCategory(Map<String, dynamic> genre) {
activeGenreId.value = genre["id"];
_loadMovieListWithGenre();
}
}
movie_repository.dart (API)
import 'package:dio/dio.dart';
import 'package:flutter_tmdb/src/models/movie.dart';
class MovieRepository {
late var _dio;
MovieRepository() {
_dio = Dio(
BaseOptions(
baseUrl: "https://api.themoviedb.org",
queryParameters: {
'api_key': '본인이 신청한 키값을 입력',
},
),
);
}
Future<List<Map<String, dynamic>>> loadGenre() async {
var response = await _dio.get('/3/genre/movie/list');
if (response.data != null) {
var data = response.data['genres'] as List;
// 모델을 만들면 아래 부분은 fromJson 으로 처리
return data.map((genre) => genre as Map<String, dynamic>).toList();// 19:20초 근처
} else {
return [];
}
}
Future<List<Movie>> loadMovieListWithGenre(int activeGenreId) async {
var response = await _dio.get('/3/discover/movie',
queryParameters: {'with_genres': activeGenreId});
if (response.data != null && response.data['results'] != null) {
var data = response.data['results'] as List;
return data.map((movie) => Movie.fromJson(movie)).toList();
} else {
return [];
}
}
}
category_movie_list.dart(view component)
import 'package:flutter/material.dart';
import 'package:flutter_tmdb/src/controller/movie_controller.dart';
import 'package:flutter_tmdb/src/models/movie.dart';
import 'package:get/get.dart';
class CategoryMovieList extends StatelessWidget {
final MovieController _movieController = Get.put(MovieController());
Widget _movieWidget(Movie movie) {
return Container(
margin: const EdgeInsets.only(top: 10),
padding: const EdgeInsets.only(right: 10),
width: 150,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(15),
child: Image.network(movie.posterUrl),
),
SizedBox(height: 10),
Text(
movie.title,
style: TextStyle(
fontSize: 12,
),
overflow: TextOverflow.ellipsis,
),
Text(
movie.voteAverage.toString(),
style: TextStyle(
fontSize: 12,
),
)
],
),
);
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text('NEW PLAYING'),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Obx(()=>Row(
children: List.generate(
_movieController.movies.length,
(movieIndex) => _movieWidget(_movieController.movies[movieIndex]),
growable: false,
),
))
),
],
),
);
}
}
여기까지 Getx 변환에 대한 설명입니다.
아래는 추가로 Getx 의 Obx 가 다시 그리는 영역에 대해서 비교해보았습니다. Obx가 _genreTag 밖에서 호출
Obx 위치를 변경하였습니다. Obx가 _genreTag 내부의 Container 에서 호출
Obx 가 Container 만 다시 생성하기 때문에 activeGenreId 값이 변한것은 인지하지만 isActive 값을 변경하지 못해서 버튼의 색상 및 글자색을 반영하지 못하는 문제가 발생한다.
'Flutter > 00 Legacy' 카테고리의 다른 글
[Flutter] Firebase SHA-1 keytool (0) | 2021.06.21 |
---|---|
[Flutter] Getx with Dependency Injection (0) | 2021.06.06 |
[Flutter] Getx with binding (4) | 2021.06.04 |
[Flutter] Getx with state management(reactive version) (0) | 2021.06.04 |
[Flutter] Getx with state management(update version) (0) | 2021.06.04 |