728x90

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

 

 

 

블로그 이미지

Link2Me

,