-
[Flutter] 로또 추천번호 생성 앱 & 애드몹 (AdMob) 광고 붙이기 [3] 편 (플러터 앱 코드)Flutter 2021. 4. 18. 14:05반응형
# [Flutter] 로또 추천번호 생성 앱 & 애드몹 (AdMob) 광고 붙이기 [3] 편 (플러터 앱 코드)
[Flutter] 로또 추천번호 생성 앱 & 애드몹 (AdMob) 광고 붙이기 [1] 편 (앱 설명 & 앱 구조)
[Flutter] 로또 추천번호 생성 앱 & 애드몹 (AdMob) 광고 붙이기 [2] 편 (백엔드서버)
1편에선 앱 설명 ,사용된 패키지, 실행화면을 포스팅 하였고 2편에선 백엔드 서버를 포스팅 하였다
이번 포스팅에선 플러터 앱 코드를 기록 하자.
1. 앱 화면
앱 메인 화면 에는
최근 당첨정보를 보여주고
QR 스캔을 통한 당첨 정보 확인
회차별 당첨번호 확인
랜덤 추천번호 받기
저장한 추천번호 목록 확인
기능이 있고
하트 아이콘은 추천번호를 받을떄마다 한개씩 사라지며
리워드 광고를 통해 충전할 수 있다
제일 하단에는 배너광고가 위치해 있다
※ 프로젝트 구조 순서대로 코드 작성
2. main.dart
앱 메인 화면 에는 에드몹 광고를 초기화 해주도록 하자
import 'package:admob_flutter/admob_flutter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_lotto_app/src/screen/Lotto_Main.dart'; import 'package:get/get.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); Admob.initialize(); runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), home: LottoMainScreen(), ); } }
3. Screen - Lotto_Main.dart
메인 스크린 코드 이다
앱이 화면이 최초 실행될때 initState 안에있는 lottoTotalResult() 함수를 호출하게 되고
최근 당첨 정보를 가져와서 data 변수에 저장하게 된다
import 'dart:convert'; import 'package:admob_flutter/admob_flutter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_lotto_app/src/utils/env.dart'; import 'package:flutter_lotto_app/src/widget/Loading.dart'; import 'package:flutter_lotto_app/src/widget/MainButton.dart'; import 'package:flutter_lotto_app/src/widget/WeekWinNum.dart'; import 'package:http/http.dart' as http; class LottoMainScreen extends StatefulWidget { @override _LottoMainScreenState createState() => _LottoMainScreenState(); } class _LottoMainScreenState extends State<LottoMainScreen> { var data; @override void initState() { // TODO: implement initState lottoTotalResult(); super.initState(); } lottoTotalResult() async { var url = Uri.parse("$app_API/recentlyData"); http.Response response = await http.get(url); if (response.statusCode == 200) { setState(() { data = jsonDecode(response.body)[0]; }); } else { // 만약 응답이 OK가 아니면, 에러 throw Exception('Failed'); } } @override Widget build(BuildContext context) { return data == null ? LoadingScreen() : Scaffold( appBar: AppBar( centerTitle: true, title: Text( '${data['drwNoDate']} / ${data['drwNo']} 회차', ), actions: [ IconButton( onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute( builder: (BuildContext context) => super.widget)); }, icon: Icon(Icons.refresh), ) ], ), body: ListView( children: [ WeekWinNumWidget( data: data, ), MainButtonWidget(), SizedBox(height: 7), Container( height: 60, child: AdmobBanner( adUnitId: adBannerUnitId, adSize: AdmobBannerSize.BANNER, ), ), ], ), ); } }
4. Screen - Lotto_WinInfo.dart
회차별 당첨정보를 보여주는 페이지
import 'package:flutter/material.dart'; import 'package:flutter_lotto_app/src/widget/WeekWinNum.dart'; class LottoWinInfoScreen extends StatelessWidget { final data; const LottoWinInfoScreen({Key key, this.data}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, title: Text( '${data['drwNoDate']} / ${data['drwNo']} 회차', ), ), body: Center( child: WeekWinNumWidget( data: data, ), ), ); } }
5. Screen - MyLottoNumData.dart
내가 저장한 추천 번호 리스트를 보여주는 화면이고 추천번호는 flutter_secure_storage 패키지를 사용해
앱 storage 에 저장되게 된다 이를통해 앱이 종료 되어도 저장된 정보는 계속 남아 있을 수 있다.
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_lotto_app/src/utils/env.dart'; import 'package:flutter_lotto_app/src/widget/Loading.dart'; import 'package:flutter_lotto_app/src/widget/LottoBallWidget.dart'; class MyLottoNumberScreen extends StatefulWidget { @override _MyLottoNumberScreenState createState() => _MyLottoNumberScreenState(); } class _MyLottoNumberScreenState extends State<MyLottoNumberScreen> { var randomLottoList; @override void initState() { // TODO: implement initState storageDataGet(); super.initState(); } void storageDataGet() async { randomLottoList = await storage.read(key: 'randomLotto'); setState(() { if (randomLottoList == null) { randomLottoList = []; } else { randomLottoList = jsonDecode(randomLottoList); } }); } void removeMyLottoNum(lottoArrIndex) async { var array = randomLottoList; array.removeAt(lottoArrIndex); if (array.length > 0) { await storage.write(key: 'randomLotto', value: jsonEncode(array)); } else { var arr = []; await storage.write(key: 'randomLotto', value: jsonEncode(arr)); } randomLottoList = await storage.read(key: 'randomLotto'); setState(() { randomLottoList = jsonDecode(randomLottoList); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, title: Text('내가 저장한 번호'), ), body: randomLottoList == null ? LoadingScreen() : randomLottoList.length == 0 ? Container( child: Center( child: Text('저장된 번호가 없습니다.'), ), ) : ListView.builder( itemBuilder: (context, index) { var lottoNum = randomLottoList[index]; lottoNum['index'] = index; return Padding( padding: const EdgeInsets.all(8.0), child: Card( clipBehavior: Clip.antiAlias, child: Row( children: [ Container( width: MediaQuery.of(context).size.width * 0.8, child: LottoBallWidget( data: randomLottoList[index], ), ), IconButton( onPressed: () { removeMyLottoNum(lottoNum['index']); }, icon: Icon(Icons.delete)) ], ), ), ); }, itemCount: randomLottoList.length, ), ); } }
6. Screen - QR_result.dart
QR을 스캔했을때 보여지는 결과 화면 페이지
import 'package:flutter/material.dart'; class QRscanResultScreen extends StatelessWidget { final data; const QRscanResultScreen({Key key, this.data}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: Text(data), ), ); } }
7. utils - env.dart
env 파일을 만들어 지속적으로 사용될 변수들을 만들어 두었다.
사용된 uinitId 는 테스트 용이며 실제 admob 광고를 붙이기 위해선 admob 회원가입후 발급 받아야 한다
본인의 unitId는 개발시 사용하면 구글에서 제제가 올수 있다고 한다
구글에선 본인이 만든 앱 광고를 본 인이 누르는걸 금지한다 나 머라나...
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; final app_API = '{서버 api url}'; final storage = new FlutterSecureStorage(); final adBannerUnitId = 'ca-app-pub-3940256099942544/2934735716'; // testBannerUnitId final adRewardUnitId = 'ca-app-pub-3940256099942544/5224354917'; // testRewardUnitId
8. utils - price_utils.dart
int 형태로 저장되어있는 당첨금액을 원 단위로 변환
import 'package:intl/intl.dart'; class PriceUtils { static final oCcy = new NumberFormat("#,###", "ko_KR"); static String calcStringToWon(String priceString) { if (priceString != null && priceString != "") { return "${oCcy.format(int.parse(priceString))} 원"; } else { return "- 원"; } } }
9. widget - Coin_widget.dart
하트 모양 코인 위젯
코인의 개수는 추천번호 저장과 마찬가지로 flutter_secure_storage 를 통해 코인개수를 저장하고 있다.
코인을 다 소모할 경우 리워드 광고를 통해 충전 할 수 있다.
import 'package:flutter/material.dart'; class CoinIconWidget extends StatelessWidget { final coinCount; const CoinIconWidget({Key key, this.coinCount}) : super(key: key); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.favorite, color: coinCount >= 1 ? Colors.red : Colors.grey, size: 35, ), SizedBox( width: 17, ), Icon( Icons.favorite, color: coinCount >= 2 ? Colors.red : Colors.grey, size: 35, ), SizedBox( width: 17, ), Icon( Icons.favorite, color: coinCount >= 3 ? Colors.red : Colors.grey, size: 35, ), SizedBox( width: 17, ), Icon( Icons.favorite, color: coinCount >= 4 ? Colors.red : Colors.grey, size: 35, ), SizedBox( width: 17, ), Icon( Icons.favorite, color: coinCount == 5 ? Colors.red : Colors.grey, size: 35, ), ], ); } }
10. widget - Loading.dart
로딩 화면 위젯
import 'package:flutter/material.dart'; class LoadingScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Container( child: Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation<Color>(Color(0xfff5a623)), ), ), color: Colors.white.withOpacity(0.8), ); } }
11. widget - LottoBallWidget.dart
로또 번호를 공 모양 형태의 위젯으로 표현하며 수자 범위에 따라 색깔을 다르게 표시한다
import 'package:flutter/material.dart'; class LottoBallWidget extends StatelessWidget { final data; const LottoBallWidget({Key key, this.data}) : super(key: key); @override Widget build(BuildContext context) { return Container( width: MediaQuery.of(context).size.width * 0.9, height: MediaQuery.of(context).size.height * 0.1, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ClipOval( child: Material( color: data['drwtNo1'] < 11 ? Colors.yellow : data['drwtNo1'] < 21 ? Colors.blue : data['drwtNo1'] < 31 ? Colors.red : data['drwtNo1'] < 41 ? Colors.grey : Colors.green, // button color child: Padding( padding: const EdgeInsets.all(10.0), child: InkWell( child: Text( data['drwtNo1'] < 10 ? '0${data['drwtNo1'].toString()}' : '${data['drwtNo1'].toString()}', style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), ), ), ), ), ), SizedBox( width: 10, ), ClipOval( child: Material( color: data['drwtNo2'] < 11 ? Colors.yellow : data['drwtNo2'] < 21 ? Colors.blue : data['drwtNo2'] < 31 ? Colors.red : data['drwtNo2'] < 41 ? Colors.grey : Colors.green, // button color child: Padding( padding: const EdgeInsets.all(10.0), child: InkWell( child: Text( data['drwtNo2'] < 10 ? '0${data['drwtNo2'].toString()}' : '${data['drwtNo2'].toString()}', style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), ), ), ), ), ), SizedBox( width: 10, ), ClipOval( child: Material( color: data['drwtNo3'] < 11 ? Colors.yellow : data['drwtNo3'] < 21 ? Colors.blue : data['drwtNo3'] < 31 ? Colors.red : data['drwtNo3'] < 41 ? Colors.grey : Colors.green, // button color child: Padding( padding: const EdgeInsets.all(10.0), child: InkWell( child: Text( data['drwtNo3'] < 10 ? '0${data['drwtNo3'].toString()}' : '${data['drwtNo3'].toString()}', style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), ), ), ), ), ), SizedBox( width: 10, ), ClipOval( child: Material( color: data['drwtNo4'] < 11 ? Colors.yellow : data['drwtNo4'] < 21 ? Colors.blue : data['drwtNo4'] < 31 ? Colors.red : data['drwtNo4'] < 41 ? Colors.grey : Colors.green, // button color child: Padding( padding: const EdgeInsets.all(10.0), child: InkWell( child: Text( data['drwtNo4'] < 10 ? '0${data['drwtNo4'].toString()}' : '${data['drwtNo4'].toString()}', style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), ), ), ), ), ), SizedBox( width: 10, ), ClipOval( child: Material( color: data['drwtNo5'] < 11 ? Colors.yellow : data['drwtNo5'] < 21 ? Colors.blue : data['drwtNo5'] < 31 ? Colors.red : data['drwtNo5'] < 41 ? Colors.grey : Colors.green, // button color child: Padding( padding: const EdgeInsets.all(10.0), child: InkWell( child: Text( data['drwtNo5'] < 10 ? '0${data['drwtNo5'].toString()}' : '${data['drwtNo5'].toString()}', style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), ), ), ), ), ), SizedBox( width: 10, ), ClipOval( child: Material( color: data['drwtNo6'] < 11 ? Colors.yellow : data['drwtNo6'] < 21 ? Colors.blue : data['drwtNo6'] < 31 ? Colors.red : data['drwtNo6'] < 41 ? Colors.grey : Colors.green, // button color child: Padding( padding: const EdgeInsets.all(10.0), child: InkWell( child: Text( data['drwtNo6'] < 10 ? '0${data['drwtNo6'].toString()}' : '${data['drwtNo6'].toString()}', style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), ), ), ), ), ), data['bnusNo'] == null ? Container() : Padding( padding: const EdgeInsets.symmetric( horizontal: 8, ), child: Icon( Icons.add, size: 20, color: Colors.red, ), ), data['bnusNo'] == null ? Container() : ClipOval( child: Material( color: data['bnusNo'] < 11 ? Colors.yellow : data['bnusNo'] < 21 ? Colors.blue : data['bnusNo'] < 31 ? Colors.red : data['bnusNo'] < 41 ? Colors.grey : Colors.green, // button color child: Padding( padding: const EdgeInsets.all(10.0), child: InkWell( child: Text( data['bnusNo'] < 10 ? '0${data['bnusNo'].toString()}' : '${data['bnusNo'].toString()}', style: TextStyle( fontSize: 17, fontWeight: FontWeight.bold), ), ), ), ), ), ], ), ); } }
12. widget - MainButton.dart
버튼 위젯과 기능들
QR스캔 회차별 당첨 정보 확인, 추천번호, 저장된 추천 번호, 리워드 광고 시청후 코인 충전 기능이 있으며
실제 앱에서 사용되는 거의 모든 기능은 이 페이지에 있다고 보면 된다
import 'dart:convert'; import 'dart:math'; import 'package:admob_flutter/admob_flutter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_lotto_app/src/screen/Lotto_WinInfo.dart'; import 'package:flutter_lotto_app/src/screen/MyLottoNumData.dart'; import 'package:flutter_lotto_app/src/screen/QR_result.dart'; import 'package:flutter_lotto_app/src/utils/env.dart'; import 'package:flutter_lotto_app/src/widget/Coin_widget.dart'; import 'package:flutter_lotto_app/src/widget/LottoBallWidget.dart'; import 'package:get/get.dart'; import 'package:logger/logger.dart'; import 'package:qrscan/qrscan.dart' as scanner; //qrscan 패키지를 scanner 별칭으로 사용. import 'package:http/http.dart' as http; class MainButtonWidget extends StatefulWidget { @override _MainButtonWidgetState createState() => _MainButtonWidgetState(); } class _MainButtonWidgetState extends State<MainButtonWidget> { AdmobReward rewardAd; var lottoData; var coinCount; @override void initState() { // TODO: implement initState rewardAd = AdmobReward( adUnitId: adRewardUnitId, listener: (AdmobAdEvent event, Map<String, dynamic> args) { if (event == AdmobAdEvent.closed) rewardAd.load(); handleEvent(event, args, 'Reward'); }, ); rewardAd.load(); getCount(); winning_result(); super.initState(); } @override void dispose() { rewardAd.dispose(); super.dispose(); } winning_result() async { var url = Uri.parse("$app_API/lottoAllList"); http.Response response = await http.get(url); if (response.statusCode == 200) { setState(() { lottoData = jsonDecode(response.body); }); } else { // 만약 응답이 OK가 아니면, 에러 throw Exception('Failed'); } } getCount() async { coinCount = await storage.read(key: 'coinCount'); if (coinCount == null) { await storage.write(key: 'coinCount', value: 5.toString()); coinCount = 5; } else { coinCount = int.parse(coinCount); } setCoinCount(coinCount); } minusCoinCount(count) async { await storage.write(key: 'coinCount', value: count.toString()); setCoinCount(count); } chargeCoinCount() async { await storage.write(key: 'coinCount', value: 5.toString()); setCoinCount(5); } setCoinCount(count) { setState(() { coinCount = count; }); } void handleEvent( AdmobAdEvent event, Map<String, dynamic> args, String adType) async { switch (event) { case AdmobAdEvent.loaded: Logger().d('admob loaded'); break; case AdmobAdEvent.opened: Logger().d('admob opened'); break; case AdmobAdEvent.closed: Logger().d('admob closed'); break; case AdmobAdEvent.failedToLoad: Logger().d('admob FailedToLOad'); break; case AdmobAdEvent.rewarded: chargeCoinCount(); showSnackBar('하트가 충전되었습니다.'); Get.back(); break; default: } } void showSnackBar(String content) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(content), duration: Duration(milliseconds: 1500), ), ); } Future qrscan() async { //스캔 시작 - 이때 스캔 될때까지 blocking String barcode = await scanner.scan(); //스캔 완료하면 결과페이지 이동 Get.to(QRscanResultScreen( data: barcode, )); } @override Widget build(BuildContext context) { return coinCount == null ? Container() : Container( width: MediaQuery.of(context).size.width * 0.8, height: MediaQuery.of(context).size.height * 0.48, child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ FlatButton( height: 45, minWidth: MediaQuery.of(context).size.width * 0.8, color: Colors.amber[100], shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.0), ), onPressed: qrscan, child: Text('QR 스캔'), ), FlatButton( height: 45, minWidth: MediaQuery.of(context).size.width * 0.8, color: Colors.amber[100], shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.0), ), onPressed: () { if (lottoData != null) { showDialog( context: context, builder: (BuildContext context) { return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20.0), ), //this right here child: Container( height: 250, child: ListView.separated( itemCount: lottoData.length, itemBuilder: (context, index) { return GestureDetector( onTap: () { Get.back(); Get.to(LottoWinInfoScreen( data: lottoData[index], )); }, child: Padding( padding: const EdgeInsets.all(15.0), child: Text( "${lottoData[index]['drwNo'].toString()} 회차", style: TextStyle( fontSize: 16, ), textAlign: TextAlign.center, ), ), ); }, separatorBuilder: (context, index) { return Padding( padding: const EdgeInsets.only( left: 15.0, right: 15.0), child: Divider(color: Colors.black45), ); }, ), ), ); }, ); } }, child: Text('회차별 당첨번호'), ), FlatButton( height: 45, minWidth: MediaQuery.of(context).size.width * 0.8, color: Colors.amber[100], shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.0), ), onPressed: () async { if (coinCount != 0) { minusCoinCount(coinCount - 1); setState(() { coinCount = coinCount - 1; }); List<int> lottoNum = []; for (var i = 0; i < 6; i++) { var lotto = Random().nextInt(45) + 1; lottoNum.contains(lotto) ? i-- : lottoNum.add(lotto); } var json = jsonEncode({ 'drwtNo1': lottoNum[0], 'drwtNo2': lottoNum[1], 'drwtNo3': lottoNum[2], 'drwtNo4': lottoNum[3], 'drwtNo5': lottoNum[4], 'drwtNo6': lottoNum[5], }); showDialog( context: context, builder: (BuildContext context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20.0), ), insetPadding: EdgeInsets.symmetric(horizontal: 20), title: Text( '행운의 추천번호 !!!\n', style: TextStyle(color: Colors.black), textAlign: TextAlign.center, ), content: LottoBallWidget(data: jsonDecode(json)), actions: [ FlatButton( child: new Text("닫기"), onPressed: () { Navigator.pop(context); }, ), FlatButton( child: new Text("번호 저장"), onPressed: () async { var array = []; var readRes = await storage.read(key: 'randomLotto'); if (readRes == null) { array.add(jsonDecode(json)); } else { array = jsonDecode(readRes); array.add(jsonDecode(json)); } await storage.write( key: 'randomLotto', value: jsonEncode(array)); Get.back(); }, ), ], ); }, ); } else { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text( '하트가 부족합니다!', style: TextStyle(color: Colors.red[400]), textAlign: TextAlign.center, ), content: Text( "광고 시청 후 하트충천 하기", textAlign: TextAlign.center, ), actions: [ FlatButton( child: new Text("닫기"), onPressed: () { Navigator.pop(context); }, ), FlatButton( child: new Text("시청하기"), onPressed: () { rewardAd.show(); }, ), ], ); }, ); } }, child: Text('랜덤 추천번호 받기'), ), FlatButton( height: 45, minWidth: MediaQuery.of(context).size.width * 0.8, color: Colors.amber[100], shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.0), ), onPressed: () => Get.to(MyLottoNumberScreen()), child: Text('내가 저장한 추천번호'), ), CoinIconWidget( coinCount: coinCount, ), ], ), ); } }
13. widget - WeekWinNum.dart
당첨 정보를 표현해주는 위젯
import 'package:flutter/material.dart'; import 'package:flutter_lotto_app/src/utils/price_utils.dart'; import 'package:flutter_lotto_app/src/widget/LottoBallWidget.dart'; class WeekWinNumWidget extends StatelessWidget { final data; const WeekWinNumWidget({Key key, this.data}) : super(key: key); @override Widget build(BuildContext context) { return Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height * 0.30, child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Text( '총 상금액 : ' + PriceUtils.calcStringToWon(data['totSellamnt'].toString()), style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), ), Text( '1등 상금액 : ' + PriceUtils.calcStringToWon(data['firstWinamnt'].toString()), style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), ), Text( '당첨인원 : ${data['firstPrzwnerCo']} 명', style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), ), LottoBallWidget( data: data, ), ], ), ); } }
14. android/app/src/main/AndroidManifest.xml
앱에서 사용되는 기능을 사용하기 위한 안드로이드 설정
permission 추가 권한 허용 (인터넷, 카메라)
<!-- 인터넷 --> <uses-permission android:name="android.permission.INTERNET"/> <!-- 카메라 --> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
meta-data 추가 (admob 사용을 위한)
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="{admob app ID}"/>
Http 주소에 접근 을 위한 application 에 android:usesCleartextTraffic 추가
<application android:label="flutter_lotto_app" android:icon="@mipmap/ic_launcher" <!-- usesCleartextTraffic 추가 --> android:usesCleartextTraffic="true">
AndroidManifest의 application 태그에서
android:usesCleartextTraffic를 true로 설정하면
모든 Http 주소에 접근할 수 있다
15. GitHub 최종 소스 코드
간단히 만들어 볼수 있었던 로또 추천번호 App 이었다.
16. 앱 출시
출시한 앱 살펴보기 (아직 수정사항이 필요한부분이 있습니다)
지적할 부분이나 궁금한 부분은 댓글 남겨주시면 확인후 수정 하도록 하겠습니다.
반응형'Flutter' 카테고리의 다른 글
[Flutter] 플러터 파이어베이스 연결 | 안드로이드 (0) 2021.04.25 [Flutter] Flutter 앱 출시 하기 (1) 2021.04.18 [Flutter] 플러터 앱 아이콘(icon) 변경하기 (1) 2021.04.18 [Flutter] 로또 추천번호 생성 앱 & 애드몹 (AdMob) 광고 붙이기 [2] 편 (백엔드서버) (0) 2021.04.18 [Flutter] 로또 추천번호 생성 앱 & 애드몹 (AdMob) 광고 붙이기 [1] 편 (앱 설명 & 앱 구조) (1) 2021.04.17