맥 원격접속은 파인더(Finder)에서 [Command+K] 로 쉽게 VNC를 실행할 수 있습니다. 이때 사용되는 디폴트 포트는 5900 입니다.
개인적으로 회사컴에 원격접속을 하기 위해서 열린 포트는 윈도우용 원격데스크톱의 포트인 3389 입니다. 그렇다 보니 윈도 접근은 쉬운데, 회사 맥에 원격접속을 하려면 절차가 까다롭습니다. 그래서 맥 VNC서버 포트 자체를 5900에서 3389로 바꾸려고 합니다.
맥 VNC 원격접속 포트를 5900에서 윈도우 원격데스크탑 포트인 3389로 변경하기
$ sudo vi /etc/services
# 5900 포트 검색 후 3389로 변경 /5900
# 기존 3389는 주석 처리
# ms-wbt-server 3389/udp # MS WBT Server
# ms-wbt-server 3389/tcp # MS WBT Server
# 5900 포트는 3389로 변경
rfb 3389/tcp vnc-server # VNC Server
rfb 3389/udp vnc-server # VNC Server
갑자기 크롬 확장 앱이 만들어 보고 싶어졌습니다. 몇년 전에 관심을 가지고 잠깐 보기는 했지만, 멀 만들지??? 하는 생각에 잠깐 보고, 이렇게 만드는 구나~~ 하고 말았는데, 유투브 동영상에 나오는 광고가 귀찮아서 없애볼까 하는 생각에 다시 크롭앱을 보기 시작했네요..
github, gitlab 등으로 소스 관리 및 배포가 대세입니다. 하지만 개발하는 OS, 플랫폼, IDE 등 사용환경에 따라 각자 관리하는, 배포에 상관없는 파일이나 디렉토리가 생깁니다. 이 파일들을 제외하기 위해서 .gitignore 파일이 사용됩니다.
파일 생성
git init
사용 방법
// flutter 프로젝트 기준으로 정리합니다.
// 지정된 파일 제외
.metadata
// 특정 패턴 파일들 제외
*.lock // lock 확장자 파일 제외
.* // . 으로 시작하는 파일 제외
// 특정 디렉토리만 삭제. 맨앞에 / 붙여야 합니다.
/android/
// 디렉토리 이하 특정 파일 제외
/android/*.lock // android/ 이하 lock 확장자 파일 들 제외
firebase 사이트는 iOS, android, Web 앱 설정만 있습니다. 저는 Flutter로 데스크탑 앱을 만들고 있는 중이라, 저의 개인적인 요구에 따라서 macOS 연동을 목표로 정리를 합니다. 하지만 iOS와 설정이 거의 동일하기 때문에 되도록 같이 설명을 하겠습니다.
로컬 머신에서 flutter 프로젝트를 생성하고, Firebase Console에 프로젝트 생성 혹은 기존 프로젝트에서 앱 추가를 합니다.
Cloud Firestore 앱 추가
iOS 앱 추가 - macos 와 공통으로 사용해야할 앱
iOS 번들 ID 확인 (혹시 이미 만들어 놓은 프로젝트에서 번들 ID를 모르신다면 참조하세요)
os(macos)/Runner.xcworkspace 프로젝트를 Xcode로 열고, 최상단 Runner 선택 후 General 탭 > Identity 영역 > Bundle Identifier 항목 확인.
Android Studio 에서 프로젝트 생성을 할때, Project Name을 입력한 다음 페이지에 Package name이 번들 ID 입니다. flutter create [project]로 직접 생성했다면, com.example.project가 기본 번들ID로 생성됩니다.
iOS/macOS 설정 및 소스 일부 변경 (Xcode 활용)
구성 파일 다운로드
GoogleService-Info.plist 파일 다운로드 및 Xcode에 추가
Runner 선택 및 오른쪽 버튼 클릭 > Add Files to "Runner" 클릭 > (Copy items if needed 옵션 선택) > GoogleService-Info.plist 선택 추 (* Runner 폴더 밑으로 추가를 해주셔야 합니다)
Firebase Emulator Suite 사용 설정
Firebase Emulator Suite는 빠르고 복잡하지 않은 설정을 지원하기 위해서 암호화 되지 않은 네트워킹 연결을 사용합니다. macOS는 기본으로 암호회된 네트워킹 연결을 요구합니다. 로컬 머신에서 개발하는 동안 Firebase Emulator Suite를 사용하려면, 작업 중인 macOS 앱에 보안이 되지 않는 로컬 네트워크 서비스에 연결하도록 허용해야 합니다.
# 추가 코드
import Firebase
FirebaseApp.configure()
# 적용 예
import Cocoa
import FlutterMacOS
import Firebase
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
FirebaseApp.configure()
return true
}
}
iOS/ macOS 빌드 시간 개선 (Podfile 변경)
현재 Firebase iOS SDK는 Xcode로 빌드시 5분 이상 걸릴 수 있는 약 500k 줄 짜리 c++ 코드에 의존하고 있습니다. 빌드 시간을 크게 단축하기 위해서, ios/Podfile에 1줄을 추가해 프리컴파일된 버전을 사용하도록 합니다.
# ios(macos) /Podfile 추가
pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '6.26.0'
// 추가 위치
# ...
target 'Runner' do
pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => $FirebaseSDKVersion
# ...
end
FirebaseSDKVersion 변수는 아래 Native SDK 버전 정의를 참조하세요.
# ios(macos)/Podfile
# Override Firebase SDK Version
$FirebaseSDKVersion = '6.33.0'
pod install 실행
실행 테스트
오류 팁
% flutter run -d macos
Changing current working directory to: /Volumes/Data/Study/flutter/nativeappmaker
Launching lib/main.dart on macOS in debug mode...
Running pod install... 1,775ms
In file included from /Users/websniper/Development/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-0.16.0+1/macos/Classes/FLTFirebaseFirestorePlugin.m:9:
/Users/websniper/Development/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-0.16.0+1/macos/Classes/Private/FLTFirebaseFirestoreUtils.h:42:4: error: expected a type
+ (FIRFirestoreSource)FIRFirestoreSourceFromArguments:(NSDictionary *_Nonnull)arguments;
^
하나의 StatefulWidget에서 상태 값을 관리하는 방법은 setState()를 사용하는 것입니다. setState() 없이 상태 값을 관리할 수도 있는데, ValueNotifier를 사용하는 것입니다. ValueNotifier가 좋은 것은 다른 위젯에서 변경한 상태값을 적용할 수 있습니다.
setState() 없이 상태값을 변경하는 예제
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ValueNotifier<int> _counter = ValueNotifier<int>(0); // ValueNotifier 변수 선언
final Widget goodJob = const Text('Good job!');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title)
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
ValueListenableBuilder(
valueListenable: _counter, // 사용할 변수를 지정. _counter가 변경 되면 자동 호출
builder: (BuildContext context, int value, Widget? child) {
// value = _counter 로 적용
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text('$value'),
child!, // child는 아래 지정된 위젯으로 치환됨
],
);
},
child: goodJob,
)
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.plus_one),
onPressed: () => _counter.value += 1, // _counter.value 값이 수정되면 자동 호출
),
);
}
}
ValueNotifier로 수정한 값을 다른 위젯에서도 사용하고 싶을때
ValueNotifier가 영향을 미치는 범위는 ValueListenableBuilder() 내 입니다. 전체 위젯에 값을 적용하기 위해서는 setState()를 사용해야 하나 그럴 경우 오류가 발생합니다. build가 완료되기 전에 setState()를 호출하게 되면 빨간 화면을 마주하게 됩니다.
이 오류를 피하면서 setState()를 사용하는 방법은 WidgetsBinding.instance.addPostFrameCallback() 입니다.
addPostFrameCallback()는 페이지 빌드 후에 비동기로 콜백함수를 호출하기 때문에 빨간 화면을 피할 수 없습니다.
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ValueNotifier<int> _counter = ValueNotifier<int>(0); // ValueNotifier 변수 선언
final Widget goodJob = const Text('Good job!');
int _count = 0;
void setCount(int counter) {
setState(() {
_count = counter + 1;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title)
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
ValueListenableBuilder(
valueListenable: _counter, // 사용할 변수를 지정. _counter가 변경 되면 자동 호출
builder: (BuildContext context, int value, Widget? child) {
// setState() 가 있는 함수를 호출
WidgetsBinding.instance.addPostFrameCallback((_) {
setCount(value);
});
// value = _counter 로 적용
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text('$value'),
child!, // child는 아래 지정된 위젯으로 치환됨
],
);
},
child: goodJob,
),
Container(
child: Text('$_count')
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.plus_one),
onPressed: () => _counter.value += 1, // _counter.value 값이 수정되면 자동 호출
),
);
}
}
댓글을 달아 주세요