News App 은 android 로 구현 테스트 해본 적이 있다.
riverpod 기능을 좀 더 익히기 위해서 검색해보니 https://www.youtube.com/watch?v=HgvKWMrbBe4 에 자료가 있더라.
원 코드 그대로 구현한 것은 아니며, Retrofit 라이브러리 사용, 디렉토리 구조는 Clean Architecture 로의 전환을 위한 걸 고려중이고, WebView 도 추가하여 실제 게시글로 이동하여 볼 수 있도록 했다.
NewsDataResult 클래스로 정의한 아래 코드가 동영상에서는 NewsModel 클래스로 정의되어 있다.
NewsDataResultBase 클래스는 테스트해보니 굳이 정의하지 않아도 되는 거 같다.
NewsDataResult.fromJson 부분이 구현의 핵심이다.
import 'package:news_app_riverpod/domain/model/news_data.dart';
sealed class NewsDataResultBase {}
class NewsDataResultLoading extends NewsDataResultBase {}
class NewsDataResultError extends NewsDataResultBase {
final String errMsg;
NewsDataResultError({
required this.errMsg,
});
}
class NewsDataResult extends NewsDataResultBase {
String? status;
int? totalResults;
List<NewsData>? articles;
NewsDataResult({
this.status,
this.totalResults,
this.articles,
});
NewsDataResult.fromJson(Map < String, dynamic > json) {
status = json['status'];
totalResults = json['totalResults'];
if (json['articles'] != null) {
articles = <NewsData> [];
json['articles'].forEach((v) {
articles!.add(NewsData.fromJson(v));
});
}
}
}
|
factory 생성자로 구현시 오류 발생 여부를 좀 더 테스트 해본 결과 정상동작됨을 확인했다.
Class 명을 NewsModel 로 변경해서 테스트를 했다. 이 사항도 GitHub 에 3rd버전으로 반영했다.
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:news_app_riverpod/domain/models/news_data.dart';
part 'news_model.g.dart';
sealed class NewsModelBase {}
class NewsDataResultLoading extends NewsModelBase {}
class NewsModelError extends NewsModelBase {
final String errMsg;
NewsModelError({
required this.errMsg,
});
}
@JsonSerializable()
class NewsModel extends NewsModelBase {
String? status;
int? totalResults;
List<NewsData>? articles;
NewsModel({
this.status,
this.totalResults,
this.articles,
});
factory NewsModel.fromJson(Map<String, dynamic> json) => _$NewsModelFromJson(json);
Map<String, dynamic> toJson() => _$NewsModelToJson(this);
/*
NewsModel.fromJson(Map < String, dynamic > json) {
status = json['status'];
totalResults = json['totalResults'];
if (json['articles'] != null) {
articles = <NewsData> [];
json['articles'].forEach((v) {
articles!.add(NewsData.fromJson(v));
});
}
}
*/
}
|
Retrofit 라이브러리를 활용하면 코드를 더 심플하게 줄일 수 있다(?).
import 'package:dio/dio.dart' hide Headers;
import 'package:retrofit/retrofit.dart';
import 'package:news_app_riverpod/domain/model/news_data_result.dart';
import 'package:news_app_riverpod/data/repository/retrofit_url.dart';
part 'rest_client.g.dart';
@RestApi(baseUrl: RetrofitURL.baseUrl)
abstract class RestClient {
factory RestClient(Dio dio, {String baseUrl}) = _RestClient;
@GET(RetrofitURL.fetchNews)
Future<NewsDataResult> fetchNews();
@GET(RetrofitURL.fetchNewsBySearching)
Future<NewsDataResult> fetchNewsBySearching(@Path() String title);
}
|
repository 정의 및 repositoryImpl 구현 => GitHub 마지막 버전에서는 삭제 처리
import 'package:news_app_riverpod/domain/model/news_data_result.dart';
abstract class NewsRepository {
Future<NewsDataResult> fetchNews();
Future<NewsDataResult> fetchNewsBySearching(String title);
}
/***
* Retrofit 라이브러리를 이용하여 구현하면 별도의 함수 정의는 하지 않아야 된다.
*/
|
import 'package:dio/dio.dart';
import 'package:news_app_riverpod/core/utils/app_utils.dart';
import 'package:news_app_riverpod/data/repository/retrofit_url.dart';
import 'package:news_app_riverpod/domain/model/news_data_result.dart';
import 'package:news_app_riverpod/domain/repository/news_repository.dart';
class NewsRepositoryImpl extends NewsRepository {
final Dio _dio = Dio(
BaseOptions(
baseUrl: RetrofitURL.baseUrl,
responseType: ResponseType.json
)
);
@override
Future<NewsDataResult> fetchNewsBySearching(String title) async {
final response = await _dio.get('/v2/everything?q='+title+'&apiKey=${AppUtils.newsKey}');
//print(response.data);
final newsDataResult = NewsDataResult.fromJson(response.data);
return newsDataResult;
}
@override
Future<NewsDataResult> fetchNews() async {
final response = await _dio.get('/v2/top-headlines?country=kr&category=science&apiKey=${AppUtils.newsKey}');
final newsDataResult = NewsDataResult.fromJson(response.data);
return newsDataResult;
}
}
|
newsProvider 코드
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:news_app_riverpod/data/datasource/news_repositoryimpl.dart';
import 'package:news_app_riverpod/data/provider/base_provider.dart';
import 'package:news_app_riverpod/domain/model/news_data_result.dart';
part 'news_provider.freezed.dart';
@freezed
class NewsState with _$NewsState {
factory NewsState({
@Default(true) bool isLoading,
required NewsDataResult newsDataResult,
}) = _NewsState;
const NewsState._();
}
final newsProvider = NotifierProvider<NewsNotifier, NewsState>(NewsNotifier.new);
class NewsNotifier extends Notifier<NewsState> {
@override
NewsState build() {
return NewsState(newsDataResult: NewsDataResult(articles: []));
}
void loadNews() async {
state = state.copyWith(isLoading: true);
NewsDataResult resp = await ref.read(restClientProvider).fetchNews();
state = state.copyWith(newsDataResult: resp, isLoading: false);
}
void loadSearchedNews(String title) async {
state = state.copyWith(isLoading: true);
//final newsResponse = await NewsRepositoryImpl().fetchNewsBySearching(title);
NewsDataResult newsResponse = await ref.read(restClientProvider).fetchNewsBySearching(title);
state = state.copyWith(newsDataResult: newsResponse, isLoading: false);
}
}
|
위 전체코드는 GitHub 에 올려두었다.
https://github.com/jsk005/Flutter/tree/main/news_app_riverpod
'Flutter 앱 > 활용예제' 카테고리의 다른 글
Flutter QR Code Scan (플러터 QR코드 스캔) (0) | 2024.02.08 |
---|---|
flutter 현재 위치좌표 가져오기 (0) | 2024.02.01 |
flutter 네이버 지도 사용하기 (0) | 2024.01.30 |
Flutter Retrofit GET, POST 예제 구현 (0) | 2023.12.22 |
Flutter WebView 4.0 예제 (0) | 2023.05.29 |