정보
- 업무명 : [Flutter] 쉽게 배우는 플러터 앱 개발 실무 FAQ와 Tips
- 작성자 : 이상호
- 작성일 : 2024.12.24
- 설 명 :
- 수정이력 :
FAQ
안드로이드 스튜디오 설치시 Hang되는 현상이 발생합니다.
- 단순히 기다려서 해결하였습니다.
안드로이드 스튜디오 설치후 이뮬레이터가 기동이 되지 않고 오류가 발생합니다. 어떻게 조치하여야 하나요?
- 이뮬레이터 기동이 되지 않고 오류가 발생하면
- BIOS의 Advanced CPU에서 Virtualization, VT-x, SVM 등을 찾아 Enable (자동으로 Enable되어 있어 BIOS에 아예 설정 메뉴가 없는 경우도 있으나 그렇지 않은 경우 기종마다 BIOS 기동방식과 메뉴가 다르니 주의하여야 함)
- 나의 HP 노트북
- 증상
- BIOS 설정을 통한 해결
- 더존 아카데미 구로의 데스크탑 중의 일부
- 증상
- BIOS 설정을 통한 해결
- 화면을 모두 다시 뜨고 마지막 설정 화면을 추가
- 그래도 안되면
- → “Windows 기능 켜기 끄기”에서 “Windows 하이퍼바이저 플랫폼” 혹은 ”가상머신플랫폼” 토글 (선택이 해제되어 있었음 → 그후도 그대로 유지하여 플러터를 정상적으로 사용하였음) → Virtual Box 가상화 소프트웨어 설치시 오류가 발생하고 속도가 너무 느려서 “Windows 하이퍼바이저 플랫폼”을 Enable하여 해결함 → WSL(Windows Subsystem for Linux) 설치시 ”가상머신플랫폼”을 인에이블하라고 하여 인에이블함 → 초기값은 선택이 해제되어 있으나 결국은 모두 선택하여 사용해야 하는 듯
안드로이드 이뮬레이터 실행시 Hang되는 현상이 발생합니다.
- 설치할 때의 디폴트 이뮬레이터인 Medium Phone API 35가 PC마다 Hang되는 현상이 있음 → 이뮬레이터를 Pixel 8 Pro로 추가하여 해결하였습니다. → 그뒤 6.5인치 폰의 대안으로 6.4인치인 Medium Phone을 사용하였는데 느리기는 해도 사용할 수준은 되었습니다. → LG 노트북의 일부 노트북에서만 Hang을 유발하고 Desktop에서는 Hang을 유발한 적이 없습니다.
- PC의 CPU와 Memory의 사용량이 급증하여 **불필요한 프로그램(카카오톡,nProtect, 등)**의 수행을 중단하여 해결하였습니다.
- Github가 설치되어 있는 경우 안드로이드 스튜디오에서 프로젝트를 만들때 Hang되는 현상이 발견되어 path 환경변수에서 Git을 제외하여 해결하였습니다. → 맥북에는 git이 기본 명령어로 설치되어 있어서 그런지 이런 현상을 발견할 수 없었습니다.
- 관리자 권한을 간혹 필요로 하는 경우가 발견되어 관리자 권한으로 실행하였습니다. (File descripter 부족 오류가 이 경우에 해당하였습니다.)
- PC나 안드로이 스튜디오를 재기동하여 해결하는 경우도 있었습니다.
- 프로젝트 새로 만들어 해결하는 경우도 있었습니다.
이뮬레이터가 주기적으로 먹통이 되거나 정상 동작하지 않습니다.
- 먼저 동작중인 이뮬레이터를 중단(ㅁ) 버튼을 눌러 실행을 중단시킵니다.
- 그후 … 버튼을 눌러 나타나는 팝업 메뉴에서 Wipe Data 메뉴를 클릭하여 이뮬레이터를 초기화합니다.
- 그후 이뮬레이터를 다시 기동하여 시도해 보기 바랍니다.
프로젝트 생성이 되지 않고 Hang되는 현상이 있습니다.
- 프로젝트 생성이 되지 않고 Hang 걸리는 화면은 아래와 같습니다.
- 이뮬레이터가 Hang되는 현상들을 함께 검토해 보기 바랍니다. C:\Program Files\Git\cmd 경로를 Path 환경 변수에서 제거한 후 정상화되는 경우가 있었습니다. → 맥북에서는 git 명령어가 기본적으로 설치되어 있어서 그런지 프로젝트 생성시 Hang되는 현상이 발생하지 않았습니다. Flutter가 C:\flutter 이외의 경로에 설치된 경우에도 프로젝트 생성시 Hang되는 현상이 발생하였습니다. → 맥북에서는 flutter가 사용자의 문서/flutter(/Users/lab4dx/documents/flutter)에 자동으로 설치되어서 그런지 프로젝트 생성시 Hang되는 현상이 발생하지 않았습니다.
- flutter create project_name 명령어로 프로젝트를 생성해 보세요. 전체 플랫폼이 추가되니 필요한 플랫폼 폴더만 남기고 제거하여 사용할 수 있습니다.
- Visual Studio Code에서는 프로젝트가 잘 만들어집니다. Visual Studio Code는 안드로이드 스튜디오처럼 IDE를 사용하여 만들지 않고 flutter create project_name 명령어로 프로젝트를 생성해서 잘 만들어지는 것으로 보입니다.
- Visual Studio Code는 또 안드로이드 스튜디오와 달리 CPU 자원을 적게 사용합니다.
pubspec.yaml 파일을 수정한 후 아래와 같은 메뉴바가 나타나는 현상이 있습니다. 어떻게 조치하여하여야 할까요?
- pubspec.yaml 파일을 수정한 후 Pub get을 해도 간헐적으로 안드로이드 스튜디오의 편집기의 상단에 아래와 같은 메뉴바가 나타나는 경우가 있는데 Pub get 작업이 정상적으로 되었다면 신경쓰지 않아도 됩니다. 아마 안드로이드 스튜디오 통합개발환경의 사소한 오류로 판단됩니다.
images/ 폴더에 등록된 이미지 Asset를 찾지 못하는 경우가 있습니다. 어떻게 조치하여야 하나요?
- pubspec.yaml 설정에 문제가 없거나 코드의 경로와 파일명 등에 이상이 없다면 안드로이드 스튜디오를 껏다 켠 후 실행해 볼 것
- pubspec.yaml에서 images/ 폴더 설정이 먹히지 않는 경우가 있어서 Image를 개별적으로 Asset으로 등록하면 해결됨
# 변경전
assets:
- images/
# 변경후
assets:
- images/다영.jpg
- images/다은.jpeg
- images/우리집 창밖.jpg
- images/정화.jpg
- images/쭈쭈.jpg
- images/콩가루 가족.jpg
- idx 환경에서는 “LG전자 DX School.PNG”와 같이 한글로 된 이미지 파일을 전혀 읽지 못하는 현상도 발견됨
주변의 Image와 넓이와 높이를 맞출때 Image에 빈공간이 나타나거나 Image가 왜곡되어 나타나는 경우가 있습니다. 어떻게 해결하여야 할까요?
- 이미지가 너무 큰 경우에는 Flutter가 아예 수용하지 못하는 현상이 있습니다.
- width나 height 인자를 사용하여 Image의 크기를 강제로 조정한 Image에 맞는 적절한 Boxfit 속성을 사용합니다.
Row(
children: [
Image.asset(
"images/다은.jpeg",
width: imageWidth3Pictures,
height: imageHeight, // 동일한 높이 설정
fit: BoxFit.cover, // 비율을 유지하면서 잘림
),
SizedBox(width: 5),
Image.asset(
"images/정화.jpg",
width: imageWidth3Pictures,
height: imageHeight, // 동일한 높이 설정
fit: BoxFit.cover, // 비율을 유지하면서 잘림
),
SizedBox(width: 5),
Image.asset(
"images/다영.jpg",
width: imageWidth3Pictures,
height: imageHeight, // 동일한 높이 설정
fit: BoxFit.cover, // 비율을 유지하면서 잘림
),
],
),
이벤트 핸들러를 지정하면 처음에 화면이 로드될 때 실행되고 정작 클릭할 때 이벤트 핸들러가 실행되지 않습니다. 무엇이 문제일까요?
- Flutter가 onTap: showFamilyDetail와 같은 형식의 문장은 이벤트 핸들러의 등록으로 인식하는데, onTap: showFamilyDetail("다은")와 같은 형식의 문장은 함수의 호출로 인식하기 때문에 발생하는 문제입니다.
// 문제가 발생한 코드
GestureDetector(
onTap: showFamilyDetail("다은"), // 이벤트 핸들러의 등록이 아니라
child: Image.asset( // 함수의 호출로 인식합니다.
"images/다은.jpg",
width: imageWidth3Pictures,
height: imageHeight,
fit: BoxFit.cover,
),
),
- 람다 함수 혹은 한줄 함수의 형식으로 이벤트 핸들러를 등록하여 해결할 수 있습니다.
GestureDetector(
onTap: () => showFamilyDetail("다은"), // 람다 함수 혹은 한줄 함수
child: Image.asset(
"images/다은.jpg",
width: imageWidth3Pictures,
height: imageHeight,
fit: BoxFit.cover,
),
),
- 익명 함수의 형식으로 이벤트 핸들러를 등록하여 해결할 수 있습니다.
// 문제가 발생한 코드
GestureDetector(
onTap: () {showFamilyDetail("다은");}, // 익명 함수
child: Image.asset(
"images/다은.jpg",
width: imageWidth3Pictures,
height: imageHeight,
fit: BoxFit.cover,
),
),
Image를 둘러싼 Container에 Border와 Border Radius를 지정했는데 나타지 않고 Image만 나타납니다. 무엇이 문제일까요?
- Image가 Container를 가리기 때문에 Border와 BorderRadius가 나타나지 않는 것입니다.
// 문제가 발생한 코드
Container(
width: 400,
height: 400,
decoration: BoxDecoration(
border: Border.all(
color: Colors.black,
width: 5,
),
borderRadius: BorderRadius.all(Radius.circular(50)),
),
child: Image.asset(
"images/${name}.jpg",
),
)
- Image를 Container의 모양과 동일한 형태로 잘라내기 위하여 ClipRRect Widget(Clip Rounded Rectangle Widget)을 사용하고, Image의 크기를 컨테이너 안에 꽉차도록 Image Widget에 fit: BoxFit.cover 인자를 설정합니다.
Container(
width: 400,
height: 400,
decoration: BoxDecoration(
border: Border.all(
color: Colors.black,
width: 5,
),
borderRadius: BorderRadius.all(Radius.circular(50)),
),
child: ClipRRect( // 이미지를 컨테이너와 동일한 모양으로 자름
borderRadius: BorderRadius.all(Radius.circular(50)),
child: Image.asset(
"images/${name}.jpg",
fit: BoxFit.cover, // 이미지가 컨테이너 안에 꽉 차도록 설정
),
),
)
context를 변수로 사용할 때 어떨 때에는 문법 오류가 발생하지 않고 어떨 때에는 오류가 발생합니다. 무엇이 문제일까요?
- StatefulWidget에서는 클래스의 어디에서나 context 변수를 사용할 수 있습니다.
- 그러나 StatelessWidget에서는 context 변수를 build() 메소드 외부에서 사용할 수 없습니다.
- 이럴 때에는 이벤트 핸들러를 정의할 때 context를 인자로 추가한 후,
... 생략 ...
showFamilyDetail(context, name) { // 이벤트 핸들러 정의시 인자로 context를 추가
Navigator.of(context).push(MaterialPageRoute(builder: (context) => FamilyDetail(name: name)));
}
... 중략 ...
child: GestureDetector( // 사용시 인자에
onTap: () => showFamilyDetail(context,"다은"),// context 추가
child: Image.asset(
"images/다은.jpg",
width: imageWidth3Pictures,
height: imageHeight,
fit: BoxFit.cover,
),
),
... 생략 ...
Color를 Colors 열거형(Enumeration) 외에 16진수 표현을 사용할 수 있나요?
- 넵, 다른 언어들과 동일하게 지원합니다. 아래와 같은 형식으로 16진수 표현의 Color를 지정할 수 있습니다.
// 16진수로 변경 전 (Colors 열거형 사용)
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
// 16진수로 변경 후
colorScheme: ColorScheme.fromSeed(seedColor: Color(0xFF673AB7)),
FloatingActionButton을 여러개 만들 수 있을까요?
- 방법 1. Row Layout 객체를 사용
floatingActionButton: Row(
children: [
FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
]
),
- 방법 2. Column Layout 객체를 사용
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
]
),
- 방법 3. Stack Layout 객체를 사용
floatingActionButton: Stack(
children: [
Positioned(
bottom: 80,
right: 10,
child: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
Positioned(
bottom: 150,
right: 10,
child: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
]
),
독립된 Page 화면으로 이동했는데 아래 화면과 같이 화면이 깨지는 현상이 나타납니다. 무엇이 잘못된 것일까요?
- 독립된 화면이 Scaffold Widget으로 시작하지 않을 때 나타나는 현상입니다.
// 문제 해결 전 코드
import 'package:flutter/material.dart';
class SimpleListViewDetailPage extends StatelessWidget {
const SimpleListViewDetailPage({super.key, required this.id, required this.password});
final int id;
final String password;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
color: Colors.blue,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"ID - $id / Password - $password",
style: TextStyle(fontSize: 30),
),
Text(
"SimpleListViewDetailPage 페이지입니다.",
style: TextStyle(fontSize: 40),
),
],
),
);
}
}
- 아래 코드와 같이 최상위 Widget을 Scaffold로 만들면 해결이 됩니다.
// 문제 해결 후 코드
import 'package:flutter/material.dart';
class SimpleListViewDetailPage extends StatelessWidget {
const SimpleListViewDetailPage({super.key, required this.id, required this.password});
final int id;
final String password;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar( // AppBar를 추가함
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(id.toString()), // title은 임시로 인자로 넘겨 받은 id를 사용함
),
body: Container(
width: double.infinity,
height: double.infinity,
color: Colors.blue,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"ID - $id / Password - $password",
style: TextStyle(fontSize: 30),
),
Text(
"SimpleListViewDetailPage 페이지입니다.",
style: TextStyle(fontSize: 40),
),
],
),
),
);
}
}
안드로이드 이뮬레이터로 이미지를 처리하는 작업 중 아래 화면과 같이 이뮬레이터가 멈춰 버리는 현상이 자주 발생합니다. 어떻게 해결하여야 할까요?
- 안드로이드 스튜디오와 idx에서 공통적으로 나타나는 현상인데 이뮬레이터로는 해결하기 어려운 문제로 보입니다. 어쩔 수 없이 스마트폰을 연결하여 테스트해야 한다고 생각합니다.
삼성폰이 Visual Studio Code와 안드로이드 스튜디오에서 인식되지 않는 현상이 있습니다.
- C:\Users\user\AppData\Local\Android\Sdk\platform-tools를 Path 시스템 변수에 추가해 보세요. 혹시 케이블이 데이터 통신이 되지 않는 케이블인지 확인해 보세요.
Visual Studio Code에서 이뮬레이터에 연결이 되지 않습니다.
- C:\Users\user\AppData\Local\Android\Sdk\platform-tools를 Path 시스템 변수에 추가해 보세요. 안드로이드 스튜디오의 이뮬레이터에 연결이 됩니다.
- + Create Android emulator를 클릭하여 이뮬레이터를 만들어서 사용합니다. 아래 화면과 같이 별도의 안드로이드 이뮬레이터에 연결할 수 있습니다.
- 그런데 결국 이뮬레이터를 사용한 개발은 느리고 오류가 많아서 교육용 외에는 사용하기 어려운 기술입니다. → 실제 기기를 연결하여 개발합시다.
Intel’s Developer Cloud (IDX)에서 Flutter 개발시 제약 사항이 어떤 것들이 있나요?
- 안드로이드 앱의 경우 프로젝트의 이름이 namespace와 applicationId와 연동되지 않고 언제나 com.example.myapp입니다. iOS 앱의 경우 어떻게 반영되는지 아직 확인되지 않고 있습니다.
- 이뮬레이터의 동작이 매우 안정적이지 않습니다. 아래 첫번째 화면과 같이 아이디와 비밀번호 사이를 키보드 포커스가 왔다 갔다하여 입력할 수 없습니다. 아래 두번째 화면과 같이 키보드 입력이 잘 이루어지지 않습니다. 아래 세번째 화면과 같이 키보드 자판이 이유없이 나타나 사라지지 않습니다. 아래 네번째 화면과 같이 이유없이 화면 하단이 깨지고 입력 필드에 있지도 않은 공란이 필드 앞쪽에 나타납니다. → 다행히 키보드 입력을 시도하거나 시간이 지나니 자동으로 해소됩니다. 그리고 Web에서는 이런 현상이 나타나는 것을 발견하지 못했습니다.
- 오랫동안 이상없이 사용되다가 이뮬레이터에 문제가 생겨서 해결되지를 않습니다. 아래 두번째 화면과 같이 Web은 문제가 없는데 안드로이드 이뮬레이터는 벌써 2일째 Starting preview가 나타나며 무한 수행만 됩니다. Firebase 설정을 여러번 바꾸면서 테스트했는데 아마 이런 형태의 문제는 안드로이드 스튜디오에서도 발생하지 않을까 생각됩니다. → flutter clean 명령을 실행하고 firebase_options.dart 파일과 firebase.json 등 Firebase 환경 설정 파일을 지우고 다시 시도해도 해결이 되지 않습니다. → 소스코드를 이관하여 해결하였습니다. (FAQ 중 “Flutter 프로젝트 이름 변경/Flutter 프로젝트 복사” 참조)
- 스마트폰을 연결하여 개발할 수 없어서 카메라를 사용한 앱의 개발이 제한적입니다. iOS 이뮬레이터는 제공되지 않으며, 안드로이드 이뮬레이터는 오류가 자주 발생하고 느립니다. 대신 안드로이드 스튜디오에서는 제공되지 않는 Web 이뮬레이터를 제공합니다.
- 무상으로 사용하는 경우 개발 가능한 최대 프로젝트의 수가 5개입니다.
- Backend Server는 로컬 네트워크에 위치하고, idx 개발환경은 클라우드의 외부망에 위치하여 로컬 네트워크에 위치한 Backend Server 접근이 가능하지 않습니다. → idx에서 ngrok를 활용하여 로컬 네트워크에 위치한 Backend Server에 접근하는 방법” Tips 참조하여 해결
- 가끔 장애가 발생하는데 장애가 발생하는 동안 개발작업을 할 수 없습니다.
- 프로젝트간에 파일과 폴더 복사 기능을 사용할 수 없습니다. 이미지와 소스코드 이관을 거의 할 수 없는 상태입니다. → 다행히 동일한 프로젝트 내에서는 복사와 이동이 잘 됩니다.
- 안드로이드 이뮬레이터에서 Tab 키가 동작하지 않습니다. → 불행히 시간이 지나도 해소되지 않습니다. 그러나 Web에서는 이런 현상이 나타나지 않습니다.
- 가상 환경이 죽어서 살지 않는 현상이 있는 듯합니다. → 프로젝트를 빠져 나갔다가 다시 들어오면 가상 환경이 다시 만들어지며 문제가 해결될 것으로 보입니다.
- 디버거를 기동했더니 Hang이 걸려 버렸습니다. (아래 첫번째 화면) 때로는 오류 메세지를 보여 주며 중단되기도 합니다. (아래 두번째 화면) → 디버거를 사용하지 못하고 print() 함수를 사용해야 할 것 같습니다. → 화면이 없이 Dart 문장만 있는 간단한 프로그램의 경우에는 디버거가 동작합니다. (아래 세번째 화면)
- 이뮬레이터가 죽은 뒤 Hard Restart를 하고 프로젝트를 다시 기동하여 가상환경을 다시 만들어도 살아 나지를 않습니다. → 브라우저를 껏다 켜서 해결하였습니다. 아마도 디버거가 Hang될 때 무엇인가가 꼬였나 봅니다.
안드로이드 스튜디오, Visual Studio Code 및 IDX 중에서 어떤 개발 도구를 사용하여야 할까요?
- 제한된 교육용 : IDX + 이뮬레이터 ← 안드로이드 이뮬레이터로 카메라 및 ios 등의 테스트 제약
- 교육용 : 안드로이드 스튜디오/Visual Studio Code + 이뮬레이터 + 실제 기기
- 개발용 : 안드로이드 스튜디오/Visual Studio Code + 실제 기기 + 이뮬레이터
- **← 이뮬레이터의 속도가 느리고 잦은 오류 / 특히 카메라 사용시**
- ** → 맥북 사용시 iOS 이뮬레이터로 증상이 완화되나 카메라 사용불가**
main.dart의 Run Configuration이 깨져서 실행되지 않습니다.
- flutter clean 명령으로 현재 build 환경을 초기화한 후 flutter run 명령으로 다시 build하여 실행해 보세요.
안드로이드 스튜디오에서 없는 파일이 있다고 오류가 발생하거나, 파일시스템에 이상이 있다는 오류 메세지가 나오는 등 프로그램 코드에 아무런 이상이 없는데 가끔 도무지 이해할 수 없는 오류가 발생합니다.
- 프로그램의 실행을 완전히 종료한 후 다시 실행하거나, flutter clean 명령어를 실행한 후 다시 실행해 보거나, Visual Studio Code로 개발도구를 전환하여 프로젝트를 실행해 보는 것도 좋은 대안입니다.
- Visual Studio Code는 Flutter 전용 도구가 아니어서 Device Manager, SDK Manager, Device Explorer와 같은 기능이 제공되지 않습니다. 하지만 플러터 개발에 최적화된 환경을 제공하며, 필요한 경우 Android Studio와 병행하여 사용할 수도 있습니다.
Dart SDK is not configured 오류가 발생하며 앱을 실행할 수 없습니다.
- Flutter가 Upgrade된 후 이전 버전의 Flutter에서 개발된 프로젝트에서 발생하는 오류입니다. 안드로이드 스튜디오의 File > Settings > Plugins로 이동하여 Flutter 플러그인을 Update합니다.
Using compileSdk 35 requires Android Gradle Plugin (AGP) 8.1.0 or higher. 오류가 발생합니다.
- Flutter Upgrade에 따라 파생되는 문제입니다.
- AGP를 업그레이드하지 않고 문제를 해결하려면 compileSdkVersion을 낮춥니다.
- android/app/build.gradle 파일 수정: compileSdkVersion을 34로 낮춥니다.
gradle
코드 복사
android {
compileSdkVersion 34
defaultConfig {
targetSdkVersion 34
}
}
Tips
안드로이드 스튜디오와 삼성폰을 연결하는 방법
- 인터넷 검색어 : 안드로이드 스튜디오 삼성폰 연결
- 첫번째 링크 : #3.안드로이드 스튜디오 스마트폰 연결하기(개발자모드 설정) (tistory.com)
- 주의 : 스마트폰 연결 후 안드로이드에서 폰이 연결되지 않는 현상이 간헐적으로 발생하는데 그럴 때에는 스마트폰의 개발자 옵션에서 USB 디버깅 옵션을 껏다 켜는 것을 반복합니다.
맥북에서 안드로이트 스튜디오와 Flutter를 설치하는 방법
- Windows PC에서 Flutter 앱을 개발하면 iOS는 테스트가 가능하지 않은 한계가 있어서 Flutter 앱의 개발은 맥북을 사용할 것을 권고드립니다. 필요할 때 인터넷 검색과 생성형 AI의 도움을 받아 개발 환경을 설정하여 사용하기 바랍니다.
- 인터넷 검색어 : 맥북 flutter 설치
- 첫번째 링크 : https://velog.io/@juheesvt/Flutter-맥-OS에서-플러터-설치하기
- 주의 : 제 기억에 위의 링크에 설명된 것보다 예외사항이 훨씬 많았습니다. 가시는 걸음 걸음 놓인 오류를 사뿐히 즈려 밟고 가야 합니다. 코드팩토리의 플러터프로그래밍 2판 28페이지 ~ 32페이지에서 여러가지 경우의 수에 맞게 상세하게 설명하고 있습니다.
맥북에서 iOS 시뮬레이터를 시작하는 방법
- 맥북의 App Store에서 Xcode 개발도구를 설치합니다.
- Xcode 개발도구에서 Xcode → open developer tool → Simulator 메뉴를 선택합니다.
- iOS 시뮬레이터에서 File → Open Simulator → iPhone 15 Pro 혹은 원하는 폰의 종류를 선택합니다.
- iOS 시뮬레이터가 기동된 것을 확인한 후 안드로이드 스튜디오에서 기동된 iPhone 15 Pro (mobile) 혹은 원하는 폰의 종류를 이뮬레이터로 선택합니다.
맥북에서 안드로이트 스튜디오와 아이폰을 연결하는 방법
- 인터넷 검색어 : 맥북 flutter 아이폰 연결
- 권한이 있는 첫번째 링크 : https://code-boki.tistory.com/110
맥북에서 안드로이트 스튜디오와 안드로이드폰을 연결하는 방법
- 안드로이드 스마트폰을 맥북에 연결 (맥도로이드) : https://mac.eltima.com/ko/connect-samsung-phone-to-mac.html#cfp-toc-anchor-0
Flutter Doctor 명령어로 설치 오류를 해결하는 방법
- 명령 프롬프트에서 flutter doctor 명령어를 실행하여 Flutter 개발 환경에 문제가 없는지 확인해 봅시다. “flutter doctor” 명령어는 Flutter 개발을 위한 안드로이드 스튜디어가 비정상 동작할 때 상태를 진단하는 유용한 명령어이니 꼭 기억해 둡시다.
- 아래 화면을 보니 정상적으로 설치되어 사용할 수 있음에도 불구하고 세개의 X 표시가 나타납니다. 안드로이드 스튜디오의 실행 로그와 마찬가지로 오류 해결의 실마리가 메세지에 들어 있는 경우가 많으니 자세히 읽어 봅시다.
- cmdline-tools component is missing, Android license status unknown, Visual Studio is missing necessary components 이렇게 3개의 오류 메세지가 나타나는데 우리가 Visual Studio 통합 개발 환경은 사용하지 않을 것이니 마지막 오류는 무시하고 첫번째와 두번째 오류를 해결하면 됩니다.
- 먼저 첫번째 오류는 명령어 도구(cmdlin-tools)가 설치되어 있지 않아 발생하는 것인데 인터넷 검색이나 생성형 AI 서비스를 활용하여 문제를 해결할 수 있을 것입니다. 인터넷에 “cmdline-tools component is missing”를 입력하여 검색해 보니 많은 분들이 해결책을 정리하여 올려 놓았습니다.
- ChatGPT와 Gemini 생성형 AI 서비스도 해결책을 정리하여 알려 줍니다.
- 다행히 인터넷 검색에 기사들이 많이 나오니 그중 첫번째 게시물을 클릭하여 해결해 보겠습니다.
- https://velog.io/@soongle/flutter-cmdline-tools-component-is-missing
- 두번째 Android license 문제는 다행히 오류 메세지 속에 해결 방법이 들어 있습니다.
X Android license status unknown.
Run `flutter doctor --android-licenses` to accept the SDK licenses.
See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.
- flutter doctor --android-licenses 명령어를 실행한 후 y를 반복하여 입력하여 라이센스 조건을 수용합시다.
- 다시 flutter doctor 명령어를 실행해 보면 Visual Studio를 제외한 모든 문제들이 사라진 것을 확인할 수 있습니다.
앱의 이름을 지정하는 방법
앱의 아이콘을 지정하는 방법
- 먼저 .png 확장자를 가지는 Image 파일로 변환합니다.
- .png 이미지 파일의 크기를 아래와 같이 각각 변환한 후
- 이름을 모두 ic_launcher.png로 바꾸어 android/app/src/main/res 폴더에 각 해상도에 맞추어 저장합니다.
- 아래는 변경전 이미지입니다.
- 아래는 변경후 이미지입니다.
안드로이드 에뮬레이터에 사진 추가하기
- 안드로이드 스튜디오의 Device Explorer를 사용하여 사진을 추가할 수 있습니다. ChatGPT에 문의하니 아래와 같이 알려 줍니다.
- 먼저 안드로이드 스튜디오에서 File > Tool > Windows > Device Explorer를메뉴를 클릭합니다.
- Device Explorer에서 sdcard > Pictures 폴더를 찾아 해당 폴더에서 마우스 우측 클릭한 후 나타나는 팝업 메뉴에서 Upload 메뉴를 클릭하여 이미지를 Upload합니다.
- 그리면 복사한 이미지가 나타나는 것을 확인할 수 있습니다.
- 복사한 사진이 나타나지 않을 때 응급조치 방법
- 1. C:\Users\user\AppData\Local\Android\Sdk\platform-tools\adb shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d [file:///sdcard/Pictures/IMG_20150514_1.jpg](file:///sdcard/Pictures/IMG_20150514_1.jpg) 과 같은 형식의 명령어로 이뮬레이터의 사진을 가상 장치에 인식시킵니다.
2. 이뮬레이터를 재시작해 봅니다.
- 1. C:\Users\user\AppData\Local\Android\Sdk\platform-tools\adb shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d [file:///sdcard/Pictures/IMG_20150514_1.jpg](file:///sdcard/Pictures/IMG_20150514_1.jpg) 과 같은 형식의 명령어로 이뮬레이터의 사진을 가상 장치에 인식시킵니다.
안드로이드 에뮬레이터 카메라 활성화
- 안드로이드 스튜디오의 Device Manager를 사용하여 사진을 추가할 수 있습니다. Device Manager에서 … 아이콘을 클릭하여 나타나는 팝업 메뉴에서 Edit 메뉴를 클릭한 후
- Show Advanced Settings 버튼을 클릭한 후
- Camera의 Front와 Back을 모두 Webcam0으로 선택합니다.
- 그러면 이뮬레이터에서 Camera 사용 승인 절차를 거쳐서
- Camera를 사용할 수 있게 됩니다.
iOS 에뮬레이터에 사진 추가하기
- iOS 14 이후 버전의 시뮬레이터에서 지원된다고 하나 15버전에서도 해당되는 메뉴를 찾을 수 없었습니다.
iOS 에뮬레이터 카메라 활성화 하기
- iOS 15버전에서도 아직 공식적으로 카메라 촬영 기능이 제공되지 않습니다.
안드로이드 에뮬레이터에 한글 추가
- 안드로이드 에뮬레이터에 한글을 추가하는 방법은 일반 스마트폰에 한글을 추가하는 방법과 동일합니다. 다만 우리가 폰을 구매할 때 한글이 자동으로 설정되어 출시되기 때문에 아래와 같은 방법으로 한글을 추가할 일이 없는 것입니다. 그러나 에뮬레이터는 기본 언어가 한글이 아니기 때문에 아래와 같은 설정을 하여야 합니다.
- 먼저 이뮬레이터의 홈버튼을 눌러 홈화면으로 이동합니다.
- 홈화면을 위로 스와이프(마우스로 화면의 중앙을 잡아 위로 끌러 올리면 됨)하여 앱목록이 나타나면 Settings 앱을 클릭합니다.
- System 메뉴를 클릭합니다.
- Languages 메뉴를 클릭합니다.
- System Languages 메뉴를 클릭합니다.
- Add a language 메뉴를 클릭합니다.
- korean으로 언어를 검색하여 한국어를 설치합니다.
- 한국어가 기본언어가 되도록 한국어를 맨위로 끌어 올립니다. 그러면 한글을 입력할 수 있는 상태가 됩니다.
안드로이드 이뮬레이터에 이미지를 저장하는 방법
이미지를 검색한 후 이미지에서 마우스를 길게 눌렀다 떼면 이미지를 다운로드하는 메뉴가 나타납니다. 이때 Download Image 메뉴를 선택하면 이미지가 이뮬레이터에 저장됩니다.
- 이뮬레이터의 홈버튼을 눌러 홈화면으로 이동한 후 화면을 마우스로 위로 끌어 올리면 설치된 앱들이 나타나는데 여기서 Photos 앱을 실행하면 다운로드된 이미지를 확인할 수 있습니다.
- 단, 이뮬레이터를 초기화하기 위하여 Wipe Data하거나 Reset하는 경우 이뮬레이터에 저장된 이미지가 사라지니 이미지나 카메라 관련한 작업을 할 때에는 이뮬레이터를 사용하지 말고 스마트폰을 직접 연결하여 사용해야 할 것을 권고드립니다.
iOS 이뮬레이터에 이미지를 저장하는 방법
- “안드로이드 이뮬레이터에 이미지를 저장하는 방법”에서 Download Image 메뉴대신 Save to Photos 메뉴를 선택한 후 Photos 앱을 실행하면 다운로드된 이미지를 확인할 수 있습니다.
- pubspec.yaml 파일에 video_player: ^2.5.1를 디펜던시로 추가합니다.
- video_player 라이브러리를 import 합니다.
import 'package:video_player/video_player.dart';
- 코드 예제 1 : Widget이 기동될 때 Network URL을 통하여 동영상을 재생하는 코드
var videoPlayerController; // VideoPlayerController 변수 정의
@override
void initState() {
// TODO: implement initState
super.initState();
// VideoPlayerController 객체 생성
videoPlayerController = VideoPlayerController.networkUrl(
Uri.parse('https://firebasestorage.googleapis.com/v0/b/totemic-fulcrum-420006.firebasestorage.app/o/uploads%2F1000002759.mp4?alt=media&token=e958a190-d6c8-4ee6-a54f-be0ec79883fa')
)
..initialize().then((_) { // VideoPlayerController 객체 초기화 완료 후 화면 갱신
setState(() {}); // then은 await와 같은 기능
})
..setLooping(true); // 동영상 반복 재생 설정
} // .. 연산자는 객체를 생성한 후 해당 객체의 메서드를 호출하거나
// 필드를 설정할 때 사용하는 약식 문법입니다.
// Cascade 연산자라고 불립니다.
- build() 함수 내에서 동영상을 보여 주기 위한 Widget은 아래와 같이 코드합니다.
videoPlayerController.value.isInitialized
? AspectRatio( // 주어진 위젯의 가로와 세로 비율을 유지하면서 크기를 조정
aspectRatio: videoPlayerController.value.aspectRatio,
child: VideoPlayer(videoPlayerController),
)
: CircularProgressIndicator(), // 초기화가 완료될 때까지 로딩 표시
- VideoPlayer Widget을 실행하고 중단하는 코드는 아래와 같은 형태로 코드합니다.
floatingActionButton: FloatingActionButton(
onPressed: () {
// 동영상 재생 및 일시정지 토글
setState(() {
videoPlayerController.value.isPlaying
? videoPlayerController.pause()
: videoPlayerController.play();
});
},
child: Icon(
videoPlayerController.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
- 코드 예제 2 : gallery_camera 프로젝트에서 동영상을 선택할 때 File 객체를 사용하여 동영상을 재생하는 코드
- VideoPlayerController 변수를 정의한 후 pickVideo() 함수에서 VideoPlayerController 객체를 초기화합니다.
var videoPlayerController;
pickVideo() async {
var picker = ImagePicker();
var pickedVideo = await picker.pickVideo(source: ImageSource.gallery);
if (pickedVideo != null) {
video = File(pickedVideo.path);
// VideoPlayerController.networkUrl을 VideoPlayerController.file로 변경
videoPlayerController = VideoPlayerController.file(video)
..initialize().then((_) {
setState(() {});
})
..setLooping(true);
}
}
takeVideo() async {
var picker = ImagePicker();
var pickedVideo = await picker.pickVideo(source: ImageSource.camera);
if (pickedVideo != null) {
video = File(pickedVideo.path);
// VideoPlayerController 객체 생성
videoPlayerController = VideoPlayerController.file(video)
..initialize().then((_) {
// VideoPlayerController 객체 초기화 완료 후 화면 갱신
setState(() {}); // then은 await와 같은 기능
})
..setLooping(true); // 동영상 반복 재생 설정
}
}
@override
void dispose()
{
// TODO: implement dispose
videoPlayerController?.dispose(); // 나중에 동영상 촬영을 하지 못하게 되는 현상 방지
super.dispose();
}
- build() 함수 내에서 동영상을 보여 주기 위한 Widget은 아래와 같이 코드합니다.
video == null
? Text('동영상을 선택하세요.')
: Column(
children: [
//Text('비디오 파일: ${video.path}'), // 삭제
videoPlayerController.value.isInitialized // 동영상 코드 시작
? AspectRatio(
aspectRatio: videoPlayerController.value.aspectRatio,
child: VideoPlayer(videoPlayerController),
)
: CircularProgressIndicator(), // 동영상 코드의 끝
ElevatedButton(
onPressed: () {},
child: Text('동영상 업로드'),
),
],
),
- VideoPlayer Widget을 실행하고 중단하는 코드는 생략합니다. 필요시 코드 예제 1을 참조하여 코드하기 바랍니다.
ngrok를 활용하여 로컬 네트워크에 위치한 Backend Server에 접근하는 방법
- ngrok 사이트로 이동하여 로그인한 후 프로그램을 다운로드합니다. Edge 브라우저에서는 프로그램의 다운로드가 허용되지 않으니 Chrome 브라우저를 사용합니다.
- 다운로드 받은 파일의 압축을 풀어 c:\windows\system32 폴더에 저장합니다.
- 명령창에서 ngrok config add-authtoken your_auth_token 형태의 명령을 실행하여 ngrok를 수행할 권한을 얻습니다. your_auth_token은 ngrok에 Login한 후 아래 화면의 아래쪽에 잘려진 부분의 위치에 개인에게 제공되는 authtoken을 사용하기 바랍니다.
- 명령창에서 ngrok http 8087 명령을 실행하여 PC의 서비스(http와 같은 특정 프로토콜의 8087과 같은 특정 포트)를 외부망에 공개합니다.
- ngrok http 8087 명령을 실행한 후 위와 같은 화면이 나타난다면 외부망에서 https://7813-121-141-142-246.ngrok-free.app로 접근하는 경우 ngrok를 실행한 PC의 http://localhost:8087로 포워딩(Forwarding)되어 접근할 수 있게 됩니다. 아래와 같은 화면이 나타나면 Visit Site 버튼을 클릭합니다.
- 단, 이 URL은 ngrok가 실행될 때마다 변경되니 한번 실행된 ngrok를 종료했다 다시 실행하면 다시 변경된 URL를 사용하여야 하니 매우 주의하여야 합니다.
- 특히 Flutter 등의 Client 프로그램을 사용하여 접근하는 경우 프로그램도 매번 수정하여야 하여 더욱 주의가 필요합니다.
flutter 프로젝트에 플랫폼을 추가하는 방법
- Flutter에서는 개별 플랫폼만 추가하는 직접적인 명령어는 제공하지 않지만 flutter create . 명령어와 같이 플랫폼 이름으로 점(.)을 사용하면 추가하지 않은 모든 플랫폼을 프로젝트에 추가할 수 있습니다. 전체 플랫폼을 추가한 후 필요한 플랫폼만 남기고 나머지 플랫폼을 삭제하기 바랍니다.
Flutter 프로젝트 이름 변경/Flutter 프로젝트 복사
- 먼저 새로운 프로젝트를 만듭니다.
- lib 폴더의 소스코드를 복사합니다.
- images 폴더와 fonts 폴더를 등 기타 자원(assets) 폴더를 모두 복사합니다.
- 앱아이콘 복원 : android/app/src/main/res 폴더에서 mipmap으로 이름이 시작하는 폴더를 복사합니다.
- 앱이름 변경 : android/app/src/main/res 폴더의 AnrdoidManifest.xml 파일에서 android:label XML 키를 앱이름으로 다시 변경합니다.
- pubspec.yaml 파일을 복사해 온 후 Pub Get 명령을 수행합니다.
- SDK Version 불일치가 발생하는 경우 flutter upgrade 명령을 사용하여 flutter 버전을 업그레이드합니다.
- test 폴더 하단의 widget_test.dart 파일에서 오류가 발생하는 경우 파일을 삭제합니다.
- 문법 오류가 발생하는 곳을 찾아 내부 모듈 import를 모두 상대 경로로 바꿉니다.
- 남아 있는 캐시와 여러가지 종속성 및 기술적인 문제의 발생 가능성이 높지만 ChatGPT와 Gemini 생성형 AI의 권고도 참고하기 바랍니다. → 아마도 위에서 설명한 1~9번의 방법을 사용하게 될 것으로 믿습니다.
- 아래는 ChatGPT의 권고입니다.
- Flutter 프로젝트의 이름을 변경하려면 아래 단계들을 따라주세요. 프로젝트 이름을 바꿀 때 여러 파일과 설정을 업데이트해야 하므로, 주의 깊게 확인하면서 진행하세요.
프로젝트 폴더 이름 변경
- 프로젝트의 루트 디렉토리 이름을 새 이름으로 변경합니다. 예를 들어, old_project_name을 new_project_name으로 바꿉니다.
pubspec.yaml 파일 수정
- pubspec.yaml 파일을 열어 name 속성을 새 프로젝트 이름으로 변경합니다.
# yaml
코드 복사
name: new_project_name
Android 설정 수정
- android/app/src/main/AndroidManifest.xml 파일에서 <application android:label="OldProjectName"> 부분을 찾아서 새로운 이름으로 변경합니다.
- android/app/build.gradle 파일의 applicationId 부분을 새 패키지 이름으로 바꿉니다 (이 작업은 패키지 이름을 바꾸는 경우에만 필요).
iOS 설정 수정
- ios/Runner/Info.plist 파일을 열고 CFBundleName 항목을 새로운 프로젝트 이름으로 업데이트합니다.
- Xcode에서 ios/Runner.xcodeproj 파일을 열고, 프로젝트 설정에서 Display Name을 새로운 이름으로 변경합니다.
프로젝트 빌드
- 변경 사항을 반영하려면 기존 빌드 캐시를 삭제하고 프로젝트를 다시 빌드하는 것이 좋습니다.
# bash
코드 복사
flutter clean
flutter pub get
iOS 및 Android 앱 이름 업데이트 확인
- Android와 iOS에서 앱이 올바른 이름으로 표시되는지 확인하려면 에뮬레이터 또는 실제 디바이스에서 실행해 보세요.
- 이 작업 후에도 특정 플랫폼에서 앱 이름이 변경되지 않으면, 캐시 문제일 수 있으므로 Flutter 프로젝트나 에뮬레이터의 캐시를 정리하고 다시 실행해 보세요.
플러터 프로젝트 이름 변경 방법
- 플러터 프로젝트의 이름을 변경하는 것은 생각보다 간단하지만, 몇 가지 주의해야 할 사항이 있습니다. 변경 과정에서 놓치기 쉬운 부분까지 포함하여 자세히 설명해 드리겠습니다.
프로젝트 폴더 이름 변경
- 터미널(또는 명령 프롬프트)을 열고 프로젝트 폴더로 이동합니다.
- mv 명령어를 사용하여 폴더 이름을 변경합니다.
# Bash
mv 기존_프로젝트_이름 새_프로젝트_이름
Android Studio 프로젝트 파일 업데이트
- Android Studio에서 변경된 프로젝트 폴더를 엽니다.
- .idea 폴더 삭제: 이 폴더는 Android Studio의 프로젝트 설정 정보를 저장하는 곳으로, 이전 프로젝트 정보를 포함하고 있으므로 삭제합니다.
- app/build.gradle 파일 수정: applicationId를 새 프로젝트 이름에 맞게 변경합니다.
# Groovy
android {
namespace 'com.example.new_app' // 새 패키지 이름으로 변경
// ...
}
- AndroidManifest.xml 파일 수정: package 속성을 새 패키지 이름으로 변경합니다.
# XML
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.new_app">
// ...
</manifest>
iOS 프로젝트 파일 업데이트
- ios 폴더 내 Runner 프로젝트 열기: Xcode에서 ios 폴더 내 Runner 프로젝트를 엽니다.
- Info.plist 파일 수정: Bundle Identifier를 새 프로젝트 이름에 맞게 변경합니다.
- Runner 타겟 설정: Runner 타겟의 Identity 탭에서 Bundle Identifier를 다시 한번 확인하고 변경합니다.
pubspec.yaml 파일 수정
- pubspec.yaml 파일을 열고 name 필드를 새 프로젝트 이름으로 변경합니다.
Flutter Pub get 실행
- 터미널에서 변경된 프로젝트 폴더로 이동하여 다음 명령을 실행하여 패키지를 다시 가져옵니다.
# Bash
flutter pub get
기타 설정 변경
- assets 폴더 경로: assets 폴더에 접근하는 코드가 있다면 해당 경로를 수정해야 합니다.
- import 경로: import 문에 사용된 경로도 새 프로젝트 이름에 맞게 변경해야 합니다.
- 기타 설정 파일: 프로젝트 설정 파일 (예: .vscode 설정 파일)에 있는 프로젝트 이름 관련 설정을 변경해야 할 수 있습니다.
주의사항
- 백업: 변경하기 전에 프로젝트를 백업하는 것이 좋습니다.
- 단축키 사용: Android Studio에서 단축키를 사용하면 더 빠르게 파일을 찾고 수정할 수 있습니다.
- 꼼꼼한 확인: 모든 파일이 정확하게 변경되었는지 꼼꼼하게 확인해야 합니다.
- 플러그인 설정: 사용 중인 플러그인 설정도 새 프로젝트에 맞게 다시 설정해야 할 수 있습니다.
추가 팁
- 스크립트 활용: 반복적인 작업을 자동화하기 위해 스크립트를 작성할 수 있습니다.
- rename 패키지: rename 패키지를 사용하여 파일 이름을 일괄적으로 변경할 수 있습니다.
- 주의: 위 과정에서 누락된 부분이나 오류가 발생할 수 있으므로 변경 전에 반드시 백업을 하고 신중하게 진행해야 합니다.
- 더 궁금한 점이 있다면 언제든지 질문해주세요.
- 참고: 위 설명은 일반적인 경우를 예시로 설명한 것이며, 프로젝트의 복잡도에 따라 추가적인 작업이 필요할 수 있습니다.
Visual Studio Code에서 Flutter 개발시의 설치 방법
- Flutter 설치
- 안드로이드 스튜디오 설치
- Visual Studio Code 설치 및 Flutter/Dart 확장 설치 (Flutter 확장을 설치하면 Dart 확장이 함께 이루어집니다.)
- 보기 > 명령 팔레트 메뉴(Ctrl+Shift+P)를 선택한 후 Run Flutter Doctor를 실행하여 설치 상태를 확인합니다.
Visual Studio Code에서 Flutter 개발시의 프로젝트 생성 방법
- 보기 > 명령 팔레트 메뉴(Ctrl+Shift+P)를 선택한 후 New Project를 실행합니다.
- 프로젝트 템플릿으로 Application을 선택합니다.
- 새 폴더 버튼을 눌러 프로젝트를 생성할 폴더를 생성한 후 선택합니다.
- 프로젝트 이름을 폴더 이름과 동일하게 입력한 후 엔터 키를 칩니다.
- Visual Studio Code의 경우 개발 대상 플랫폼을 선택하는 기능이 없어 android, ios, linux, macos, web 및 windows 폴더가 모두 만들어집니다. 원하지 않는 개발 플랫폼의 폴더를 삭제한 후 사용하기 바랍니다 .
Visual Studio Code에서 실행 플랫폼 선택 방법
- Visual Studio Code의 우측하단에서 No Device 버튼을 클릭하면 이뮬레이터를 포함하여 실행이 가능한 Chrome, Edge, Windows 및 SM A235N과 같이 PC에 연결된 폰 등의 장치가 나타납니다.
참고 문헌
[논문]
- 없음
[보고서]
- 없음
[URL]
- 없음
문의사항
[기상학/프로그래밍 언어]
- sangho.lee.1990@gmail.com
[해양학/천문학/빅데이터]
- saimang0804@gmail.com
'프로그래밍 언어 > Flutter' 카테고리의 다른 글
[Flutter] 쉽게 배우는 플러터 앱 개발 실무 05강 (0) | 2025.01.11 |
---|---|
[Flutter] 쉽게 배우는 플러터 앱 개발 실무 04강 (0) | 2025.01.04 |
[Flutter] 쉽게 배우는 플러터 앱 개발 실무 03강 (2) | 2024.12.28 |
[Flutter] 쉽게 배우는 플러터 앱 개발 실무 02강 (0) | 2024.12.21 |
[Flutter] 쉽게 배우는 플러터 앱 개발 실무 01강 (2) | 2024.12.21 |
최근댓글