리액트 네이티브가 나쁘진 않지만, 저는 두가지 이유로 플러터를 선호 합니다
- UI를 구성하면, 무조건 aos와 ios에서 동일하게 보인다. (플러터 프레임워크가 OS 기본 컨트롤을 사용하지 않음)
- aos, ios 뿐만 아니라 웹, Windows, macOS 앱도 한 소스로 관리가 가능하다. (모든 클라이언트 개발을 플러터로 끝내 버리고 싶다)
물론, 단점도 있습니다. 장단점 관련해서는 이 영상을 참조하세요. - https://youtu.be/l05wkkCCe2Y
프로그래밍 공부를 하는데 가장 좋은 방법은 직접 만들어 보는 것이죠.
해서 자바 스크립트와 파이썬을 이용해 만들었던 웹 사이트를 플러터 앱으로 만들어 보기로 했습니다. 첫 화면만 만들고 상세 화면은 만들어둔 웹 사이트로 연결 했더니 구글 플레이에서도 승인 거부, 애플 앱스토어에서도 승인 거부
첫 화면만 앱으로 만들고
웹사이트로 연결하는 방식의 앱은 양쪽 모두 승인을 안 해주더군요.
그래서 모든 기능을 플러터로 다시 다 구현하고, 기왕 만든 앱 수익이 조금이라도 생기면 좋을 것 같아 애드몹을 붙였습니다
플러터 앱 만들기
플러터 앱 만들기 무료 강의 추천 - https://nomadcoders.co/flutter-for-beginners
플러터 앱 개발에 필요한 주요 내용을 모두 다루고 있습니다.
API를 호출해서 데이터를 읽어오고, 읽어 온 데이터를 화면에 뿌려주는 주요 내용들을 담고 있습니다.
간단한 앱을 만들어 구글 플레이와 애플 앱스토어에 배포하는데 아주 딱 좋은 무료 강의, 강추합니다.
개발툴은 Visual Studio Code를 사용했습니다.
안드로이드 스튜디오를 사용하면 편리하긴 한데, 좀 무거워서리. VS code가 가볍고 개발 진행하는데 부족함 없는 환경을 제공합니다.
안드로이드 스튜디오는 관련된 모든 기능을 UI로 제공해줘서 편하고
VS code는 경우에 따라 콘솔에서 명령어를 쳐야 하긴 하지만, 개인적으로는 가볍고 경쾌한 VS code가 더 좋더군요.
VS code를 플러터 앱 개발에 편하게 사용하기 위한 세팅 방법은 위의 추천 강의 내용에 자세히 설명 됩니다.
아래 링크에 별도로 정리해둔 내용을 참조하셔도 좋고요.
플러터 개발환경 세팅을 위한 내용은 아래 링크 참조 - https://madchick.tistory.com/185
데이터 읽어 오기
데이터를 읽어 오는 패키지들은 많은데, 이번에는 http를 사용했습니다.
샘플 코드는 아주 간단.
api_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:dearabby/models/detail_model.dart';
class ApiService {
static const String baseUrl = "데이터를 읽어올 API 주소";
static Future<DetailModel> getDetailInfoById(String id) async {
final url = Uri.parse("$baseUrl/$id.json");
final response = await http.get(url);
if (response.statusCode == 200) {
final detailInfo = jsonDecode(utf8.decode(response.bodyBytes));
return DetailModel.fromJson(detailInfo);
}
throw Error();
}
}
요즘 API들은 거의 대부분 json 타입으로 리턴을 하니
모델 클래스에 json을 넘기고 해당 json 구조에 맞춰 모델 클래스에 데이터가 담기게 됩니다.
상세한 내용은 위에서 추천한 플러터 앱 개발 강의에서 자세히 설명해 줍니다.
json 구조가 조금 달라서 거기에 맞춰 수정한 정도만 다릅니다.
기본기 다지기는 위에서 추천한 니꼬쌤 강의에서~
detail_model.dart
class DetailLine {
final String linesSec1, linesSec2;
DetailLine.fromJson(Map<String, dynamic> json)
: linesSec1 = json['linesSec1'],
linesSec2 = json['linesSec2'];
}
class DetailModel {
final String title, info, image1, image2;
final List<DetailLine> lines;
DetailModel({this.title, this.info, this.image1, this.image2, this.lines});
factory DetailModel.fromJson(Map<String, dynamic> json) {
var list = json['lines'] as List;
List<DetailLine> lineList =
list.map((i) => DetailLine.fromJson(i)).toList();
return DetailModel(
title: json['title'],
info: json['info'],
image1: json['image1'],
image2: json['image2'],
lines: lineList);
}
}
구글 TTS API - Cloud Text-to-Speech API
웹으로 만들 때는 영어 읽는 것은 구글 TTS, 한글 읽는 것은 카카오 TTS를 이용했는데, 자바 스크립트로 만들었던 것을 dart 로 똑같은 걸 한번 더 만들라니 짜증나서 모바일 앱에서는 한글, 영문 모두 그냥 구글 TTS API를 사용했습니다.
구글, 카카오 계정만 만들면 이런 걸 무료로 사용할 수 있다니 차암 개 편한 세상
구글 계정 만들고 구글 클라우드 가면 사용할 수 있는 API들 겁나 많습니다. 너무 많아서 도대체 뭘 어찌해야 할지 모를지경
가입하고 설정하면 키값을 하나 줍니다. 호출 할 때 내 키값으로 호출하면 끝.
http 호출 시 body 에 json 으로 설정값을 넘기면 됩니다.
호출이 잘 되면 음성 데이터가 mp3 포멧으로 base64 인코딩 된 형태로 리턴 됩니다.
그럼 이거 받아서 임시 저장해 두고, 재생하면 끝
사용된 패키지들은 아래와 같습니다.
http: ^0.13.5 - 구글 API 호출하기 위해서
just_audio: ^0.9.15 - mp3 파일 재생
path_provider: ^2.0.12 - 음성 데이터 임시 저장
uuid: ^3.0.6 - 임시 저장용 파일명을 위해
import 'package:http/http.dart' as http;
import 'dart:io';
import 'dart:convert';
import 'package:just_audio/just_audio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:uuid/uuid.dart';
onButtonTap() async {
final url = Uri.parse(
'https://texttospeech.googleapis.com/v1/text:synthesize?key=내키값');
Map<String, String> headers = {
"Authorization": "Bearer",
"Content-type": "application/json; charset=utf-8"
};
var json =
'{"audioConfig": {"audioEncoding": "MP3","pitch": 0,"speakingRate": 1}, "input": {"text": "${lines.trimLeft()}"}, "voice": {"languageCode": "ko-KR","name": "ko-KR-Wavenet-A"}}';
final response = await http.post(url, headers: headers, body: json);
if (response.statusCode == 200) {
Map<String, dynamic> jsonData = jsonDecode(response.body);
var audioData = jsonData['audioContent'];
debugPrint(audioData);
AudioPlayer audioPlayer = AudioPlayer();
final List<int> bytes = base64.decode(audioData);
Directory tempDir = await getTemporaryDirectory();
var uuid = Uuid();
String tempPath = '${tempDir.path}/${uuid.v4().toString()}.mp3';
await File(tempPath).writeAsBytes(bytes);
await audioPlayer.setFilePath(tempPath);
await audioPlayer.play();
await File(tempPath).delete();
}
}
네, 구글 TTS API 무조건 공짜 아닙니다.
하루 할당된 일정량 이상의 콜이 날아오면 초과분 만큼 돈 내야 합니다.
해서 나중에 한번 호출한 것은 데이터 저장해 두었다가 다시 호출하지 않도록 기능을 개선할 예정입니다
만, 귀찮아서 과연 할지는 알 수 없습니다.
플러터에 애드몹 광고 붙이기
플러터에 애드몹 붙이는건 정말 간단하더군요.
구글 애드몹 팀에서 만든 아래 영상을 따라하면 됩니다. 글로 너저분 하게 정리하는 것 보다 유튜브 영상 따라하는게 훨씬 간단합니다.
가장 어려운 문제는 구글 계정으로 애드몹 가입한 후에 승인을 기다리는 일 입니다.
빨리 해보고 싶은데, 승인이 안 나는거죠.
아무튼, 승인만 나면 뭐 유튜브 영상에서 하라는대로만 하면 아주 간단하게 해결 됩니다.
구글 플레이나 애플 앱스토어에 스크린샷 등록 할 때
애드몹 광고가 테스트 모드로 뜬 상태에서 캡쳐하면 승인이 안됩니다.
차라리 광고가 표시되지 않도록 한 후에 등록용 스크린샷을 캡쳐 하는 것이 좋습니다.
TEST 라는 문자가 찍히거나 완성된 앱의 모습이 아닌 것 같으면 절대 승인이 안 떨어집니다.
시뮬레이터나 실물 폰으로 테스트 할 때 광고가 나타나지 않는 경우가 있습니다.
걱정할 필요 없습니다. 구글 플레이, 애플 앱스토어 등록을 마친 후에 애드몹 관리자 화면에서 앱 등록 설정을 마치고 나면 정상적으로 표시 됩니다.
주의점
본인이 사용하는 핸드폰 들을 테스트 기기로 꼭 등록하셔서 불이익을 당하지 않도록 하세요.
내가 개발을 위해 사용하는 폰을 등록 해두면, 구글 애드몹이 이 폰은 테스트 기기라고 생각하고 광고 계산에서 제외시킨다고 합니다.
플러터 앱, 구글 플레이, 애플 앱스토어에 배포
뭐, 플러터로 만든 앱이라고 해서 구글 플레이와 애플 앱스토어에 배포하는 방법이 다른건 아닙니다.
배포가 힘든건, 등록하는데 필요한 파일들이 너무 많다는 것.
디자이너가 도와주면 좋겠지만 혼자 앱 개발 하는 사람들에게는 정말 어려운 관문 입니다.
아이콘은 아이콘 만들어주는 웹 사이트에서 만들었습니다.
스크린샷은 안드로이드, iOS 시뮬레이터를 이용해서 캡쳐 했습니다.
스크린샷 때문에 몇번을 빠꾸 먹었는지 모르겠네요. ㅋㅋ
간신히 승인 통과하고 광고도 붙인 결과 입니다.
확실히 요즘은 블로그 보다는 유튜브 영상을 보고 따라하는게 훨씬 빠르고 편한 것 같네요.
아, 나도 이제 유튜버로 전향을 해야 하나.
애플 앱스토어 배포는 비교적 간단합니다.
ios/Runner.xcworkspace 파일을 더블 클릭하면 Xcode가 열릴겁니다.
Product > Archive 클릭한 후 화면에 나오는 대로 next 만 열심히 클릭하면 됩니다.
빌드가 안되면 짜증이 슬슬 올라오기 시작하는데, 방법 없습니다. 열심히 구글링 하면서 이슈 해결 해야지요.
next 열심히 눌러 앱스토어 업로드 되면, 그 다음 부터는 앱스토어 관리자 웹사이트에서 모두 진행됩니다.
일단은 TestFlight 메뉴로 올라 가는데, 테스트 진행 안하고 바로 심사 신청해도 됩니다.
그래도 TestFlight 앱 깔아서 실물 아이폰에서 테스트 한번 해보면 좋겠지요.
애플 개발자 등록 안해 보셨고
CocoaPod 꼬이고 그럼 헬이 펼쳐지는데, 음.. 네이티브 개발 지식이 어쩔 수 없이 필요합니다.
그래도 넘 걱정 마시실. 에러메시지 등으로 구글링 하면 다 나옵니다.
나만 격는 문제가 아니니까요.
안드로이드는 뭐 좀 해줄게 많습니다.
일단 서명을 위한 키파일 생성을 해야합니다. (애플처럼 제발 좀 알아서 해줬으면 좋으련만..)
콘솔 명령어로 키 생성 - android/app/keystore.jks
keytool -genkey -v -keystore keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload
암호 입력하라고 나오는데, 잘 기억해 둡니다.
두개 입력해야 하는데, 저는 귀찮아서 둘 다 동일하게 입력 했습니다.
android/key.properties 파일 만들기
storePassword=위에서 입력한 암호 기입
keyPassword=위에서 입력한 암호 기입
keyAlias=upload
storeFile=keystore.jks
android/app/build.gradle 파일 편집
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
시뮬레이터나 폰을 연결해서 테스트 해볼 때는 디버그로 빌드가 되는데
다른 사람 폰에 설치해서 테스트 할 때는 릴리즈로 빌드해서 주면 좋습니다.
릴리즈 빌드
flutter build apk --release --target-platform=android-arm64
/build/app/outputs/flutter-apk/ 폴더에 파일이 생깁니다.
app-release.apk 파일을 전달하면 됩니다.
구글 플레이 업로드용 빌드
flutter build appbundle
build/app/outputs/bundle/release/ 폴더에 파일이 생깁니다.
app-release.aab 파일을 구글 플레이 웹사이트에 업로드 하면 됩니다.
이 모든 과정을 관리할 수 있는 프로그램도 있던데
많은 앱을 자주 릴리즈 해야하는 경우는 사용하면 좋을 것 같습니다.
하지만, 저는 아직은 뉴비 1인 개발자라 그냥 노가다 하고 있습니다.
Dear Abby 기사 듣기 - 디어 애비 영문 기사를 클릭하면 읽어 줍니다. 영어 리스닝 연습용 입니다.
한국 드라마 명대사 듣기 - 제가 정리한 드라마 명대사를 한글로 읽어 줍니다. 외국인을 위한 한국어 공부(?) 앱 입니다.
역시 맨땅에 모조리 혼자 다 하는건 빡시다.
별 사소한 부분에서도 삽질이 반복되는 등등.
그래도 함 해보면 뿌듯해.
유튜브는 정말이지 인류의 축복.
좋은 콘텐츠 생산하는 모든 유튜버들에게 감사를~
앞으로 기능 개선들을 진행해야 하는데, 아~ 시간을 낼 수 있을란지
- 전체듣기 기능
- 반복 듣기 기능
- 즐겨찾기 기능, 즐겨찾기 모아 보기
- 한번 들은 문장은 폰에 저장해서 오프라인에서도 들을 수 있도록 개선
1인 개발자 모두 모두 홧팅~~