안녕하세요.
이번 포스팅에서는 dio를 사용한 API request를 구현해 보겠습니다.
언어: dart
IDE: Android Studio
Framework: Flutter
Test device: Android
Dio는 Flutter에서 네트워크 요청을 수행하기 위해 사용되는 효과적인 HTTP 클라이언트 라이브러리입니다. 유연성, 사용 편의성이나 사용자 지정 헤더, 요청/응답 처리와 같은 고급 기능을 지원하는 점에서 널리 사용됩니다.
추가로, 이전에 포스팅했던 bloc pattern을 적용해서 예제를 작성하겠습니다.
https://it-of-fortune.tistory.com/49
먼저, 아래 명령어로 dio를 추가해 줍니다.
flutter pub add dio
다음은 아래와 같이 폴더와 파일을 구성해 줍니다(figure 2).
이제 응답받은 결과를 넣을 model class를 생성합니다.
lotto_model.dart
class LottoResult {
final String drawDate;
final int totalSellAmount;
final int firstWinAmount;
final int firstPrizeWinners;
final List<int> winningNumbers;
final int bonusNumber;
final int drawNumber;
LottoResult({
required this.drawDate,
required this.totalSellAmount,
required this.firstWinAmount,
required this.firstPrizeWinners,
required this.winningNumbers,
required this.bonusNumber,
required this.drawNumber,
});
factory LottoResult.fromJson(Map<String, dynamic> json) {
return LottoResult(
drawDate: json['drwNoDate'],
totalSellAmount: json['totSellamnt'],
firstWinAmount: json['firstWinamnt'],
firstPrizeWinners: json['firstPrzwnerCo'],
winningNumbers: [
json['drwtNo1'],
json['drwtNo2'],
json['drwtNo3'],
json['drwtNo4'],
json['drwtNo5'],
json['drwtNo6'],
],
bonusNumber: json['bnusNo'],
drawNumber: json['drwNo'],
);
}
}
API 호출을 진행할 service를 생성합니다.
lotto_service.dart
import 'dart:convert'; // Import for JSON decoding
import 'package:dio/dio.dart';
class LottoService {
final Dio _dio = Dio();
LottoService() {
_dio.options.headers = {
'Content-Type': 'application/json',
};
}
Future<Map<String, dynamic>> fetchLottoData(int drawNumber) async {
const String baseUrl = 'https://www.dhlottery.co.kr/common.do';
final params = {
'method': 'getLottoNumber',
'drwNo': drawNumber,
};
try {
final response = await _dio.get(baseUrl, queryParameters: params);
// Decode if the response is a JSON string
final rawResponse = response.data;
final data = rawResponse is String ? json.decode(rawResponse) : rawResponse;
// Check for success
if (data['returnValue'] == 'success') {
return data;
} else {
throw Exception('Failed to fetch data: ${data['returnValue']}');
}
} catch (e) {
throw Exception('Error: $e');
}
}
}
데이터 요청과 수신을 위한 준비는 끝났습니다. 여기서 우리는 호출부터 응답을 그려주는 것까지 bloc pattern을 통해 진행되도록 구현해 보겠습니다.
- lotto_state.dart
- lotto_event.dart
- lotto_bloc.dart
위 파일들을 각각 알맞은 폴더 내에 생성해 줍니다.
우선 state입니다.
lotto_state.dart
import '../model/lotto_model.dart';
abstract class LottoState {}
class LottoInitial extends LottoState {}
class LottoLoading extends LottoState {}
class LottoLoaded extends LottoState {
final LottoResult result;
LottoLoaded(this.result);
}
class LottoError extends LottoState {
final String message;
LottoError(this.message);
}
event입니다.
lotto_event.dart
abstract class LottoEvent {}
class FetchLotto extends LottoEvent {
final int drawNumber;
FetchLotto(this.drawNumber);
}
다음은 bloc 부분입니다.
lotto_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import '../event/lotto_event.dart';
import '../model/lotto_model.dart';
import '../service/lotto_service.dart';
import '../state/lotto_state.dart';
class LottoBloc extends Bloc<LottoEvent, LottoState> {
final LottoService lottoService;
LottoBloc(this.lottoService) : super(LottoInitial()) {
on<FetchLotto>(_onFetchLotto);
}
Future<void> _onFetchLotto(FetchLotto event, Emitter<LottoState> emit) async {
emit(LottoLoading());
try {
final data = await lottoService.fetchLottoData(event.drawNumber);
final result = LottoResult.fromJson(data);
emit(LottoLoaded(result));
} catch (e) {
emit(LottoError(e.toString()));
}
}
}
이제 준비가 끝났으니 UI 코드를 작성해 보겠습니다.
lotto_result_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/lotto_bloc.dart';
import '../event/lotto_event.dart';
import '../service/lotto_service.dart';
import '../state/lotto_state.dart';
class LottoScreen extends StatelessWidget {
final LottoService lottoService;
LottoScreen({Key? key, required this.lottoService}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LottoBloc(lottoService),
child: Scaffold(
appBar: AppBar(
title: Text(
'Lotto Results',
style: TextStyle(color: Colors.white),
),
),
body: LottoView(),
),
);
}
}
class LottoView extends StatefulWidget {
@override
_LottoViewState createState() => _LottoViewState();
}
class _LottoViewState extends State<LottoView> {
final TextEditingController _drawNumberController = TextEditingController();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _drawNumberController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: '회차를 입력하세요.',
labelStyle: TextStyle(color: Colors.grey[600]),
),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () {
final drawNumber = int.tryParse(_drawNumberController.text);
if (drawNumber != null && drawNumber > 0) {
context.read<LottoBloc>().add(FetchLotto(drawNumber));
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('유효한 회차를 입력하세요.')),
);
}
},
child: Text('결과 불러오기'),
),
const SizedBox(height: 20),
Expanded(
child: BlocBuilder<LottoBloc, LottoState>(
builder: (context, state) {
if (state is LottoInitial) {
return Center(
child: Text(
'결과 없음.',
style: TextStyle(color: Colors.grey[600], fontSize: 16),
),
);
} else if (state is LottoLoading) {
return Center(child: CircularProgressIndicator());
} else if (state is LottoLoaded) {
final result = state.result;
return SingleChildScrollView(
child: Card(
elevation: 5,
margin: EdgeInsets.all(10),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Draw Number: ${result.drawNumber}', style: TextStyle(fontSize: 18)),
Text('Draw Date: ${result.drawDate}', style: TextStyle(fontSize: 18)),
Text('Total Sell Amount: ${result.totalSellAmount}', style: TextStyle(fontSize: 18)),
Text('First Prize Amount: ${result.firstWinAmount}', style: TextStyle(fontSize: 18)),
Text('First Prize Winners: ${result.firstPrizeWinners}', style: TextStyle(fontSize: 18)),
Text('Winning Numbers: ${result.winningNumbers.join(', ')}', style: TextStyle(fontSize: 18)),
Text('Bonus Number: ${result.bonusNumber}', style: TextStyle(fontSize: 18)),
],
),
),
),
);
} else if (state is LottoError) {
return Center(
child: Text(
'Error: ${state.message}',
style: TextStyle(color: Colors.red),
),
);
}
return Container();
},
),
),
],
),
);
}
}
마지막으로 main 부분입니다.
main.dart
import 'package:flutter/material.dart';
import 'package:for_practice/src/screen/lotto_result_screen.dart';
import 'package:for_practice/src/service/lotto_service.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.lightBlue,
appBarTheme: AppBarTheme(
color: Colors.lightBlue,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.lightBlue, // Button color
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.grey[200],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide.none,
),
),
scaffoldBackgroundColor: Colors.grey[100],
),
home: LottoScreen(lottoService: LottoService()),
);
}
}
사용자에게 번호를 입력받아 해당하는 회차의 데이터를 불러와 UI에 표기해 주는 어플입니다.
테스트해 보겠습니다.
정상적으로 받아오고 있습니다.
이상 포스팅을 마치겠습니다.
감사합니다.