지난 화요일(12월 3일) 데브기어 라운지에서 첫번째 "델파이 마이그레이션 DAY"를 진행했습니다.
마이그레이션 데이는?
최근 윈도우 10 지원과 애플리케이션 현대화를 위한 마이그레이션 및 업그레이드를 시작하는 개발자들의 고민과 질문을 함께 고민하는 자리입니다. 해당 행사는 정기적으로 매월 첫번째 화요일에 데브기어 라운지에서 오프라인으로 진행합니다. 단순히 세미나 형식으로 정보를 받는것이 아닌 서로 자유롭게 정보와 의견을 나누는 커뮤니케이션 시간입니다.
이 도구를 이용해 다수의 소스파일과 다수의 컴포넌트 그리고 소스코드를 일관되게 전환할 수 있습니다.
마이그레이션 자동화 도구 제작 계기
이미 엠바카데로에서는 reFind라는 마이그레이션 자동화 도구를 제공합니다. reFind는 소스코드를 분석해 컴포넌트를 변경하고, 속성을 변경하고, 유즈절을 정리하는 등의 기능을 제공합니다. 하지만, reFind는 일대일로 매칭되는 컴포넌트와 속성만을 변경할 수 있습니다.
이 도구를 제작한 계기는 리얼그리드(TRealGrid)를 퀀텀그리드(TcxGrid)로 전환이 필요한 컨설팅 프로젝트였습니다.
퀀텀그리드는 기본적으로 3개의 컴포넌트 셋(그리드, 레벨, 뷰)으로 그리드를 구성합니다. 즉, 하나의 리얼그리드를 3개의 그리드 셋으로 변경해야 했고, 컬럼의 경우도 리얼그리드는 목록 속성으로 퀀텀그리드는 객체 목록으로 구성되어 reFind 기능으로 부족했습니다.
리얼그리드를 퀀텀그리드로 변환하는 작업이 작은수였다면 복잡하더라도 수작업으로 진행했을 것입니다. 하지만 해당 프로젝트에서는 천여개의 화면에 수백여개의 리얼그리드를 사용했고, 그리드를 사용하는 코드는 몇만 줄이었습니다. 이 코드를 수작업으로 진행한다면 작업기간이 오래걸릴 것은 물론, 여러명이 작업 시 서로 다른 방식으로 작업하는 등 일관되게 작업하기 어려웠습니다.
그래서, 기존 소스코드를 분석해 원하는 형태로 일관되게 재작성하기 위한 마이그레이션 자동화 도구를 작성했습니다.
비슷한 내용의 작업이 필요한 분들을 위해, 마이그레이션 자동화 도구를 오픈소스로 공개합니다.
마이그레이션 자동화 도구는 제공되는 소스코드를 그대로 사용할 수 없습니다. 컴포넌트와 소스코드는 사용자에 따라 매우 다양하게 사용됩니다. 마이그레이션 작업은 사용자의 패턴에 맞춰 변환하게 되고 이 도구에 구현된 코드들도 제가 진행한 프로젝트에 맞게 커스터마이징된 것입니다.(참고를 위해 그대로 등록합니다.)
즉, 여러분들이 위 오픈소스를 활용하려면, 마이그레이션 자동화 도구의 구조를 어느정도 이해하고 여러분들의 소스코드의 패턴과 사용방법에 맞춰 변환작업을 위한 코드를 커스터마이징해야 합니다. 다음 설명을 통해 마이그레이션 도구의 원리와 구조를 설명합니다.
마이그레이션 자동화 도구의 원리
마이그레이션 자동화 도구의 구조와 컨버터
마이그레이션 자동화 도구의 원리
마이그레이션 자동화 도구의 주요 목적은 컴포넌트 변경을 자동화하는 것입니다. 컴포넌트 변경을 자동화하기 위해서는 파일을 읽어 정보를 탐색 및 분석 후 변환할 컴포넌트 형식으로 재작성 과정이 필요합니다.
컴포넌트 정보는 폼파일(*.dfm)과 소스파일(*.pas)에 있으며 컴포넌트 변경은 다음 두가지 작업을 통해 진행해야 합니다.
폼파일 전환작업
폼파일은 dfm 확장자를 갖는 파일로 내부적으로 다음과 같은 오브젝트 텍스트 포맷의 텍스트 파일로 구성됩니다.
오브젝트 텍스트 포맷의 특징은 다음과 같습니다.
컴포넌트 정보는 object로 시작합니다.
들여쓰기를 통해 시작과 끝(end)을 구분합니다.
속성은 "이름 = 값" 형식으로 구성됩니다.
컴포넌트의 목록 속성은 목록이름 은 "이름 = <>" 형식을 갖습니다.
마이그레이션 자동화 도구는 위의 오브젝트 텍스트를 분석하는 파서를 포함하고 있습니다.
파서를 통해 컴포넌트 내용을 분석 후 새로운 컴포넌트 정보로 재작성해 파일에 덮어쓰게 됩니다.
2) ].ReadOnly를 찾아 ].Properties.ReadOnly로 변경해야 합니다.
(주의할 점은 대소문자를 구분하지 않고 이 작업을 진행해야 합니다.)
위 과정을 정규표현식으로 처리한 코드는 다음과 같습니다.(자세한 내용은 뒤에서 다시 설명합니다.)
function TColumnsConverter.ConvertColumnsItems(ASrc: string;
var ADest: string): Boolean;
const
SEARCH_PATTERN = '(' + GRIDNAME_REGEX + '\.[Cc]olumns\[|' + GRIDNAME_REGEX + '\.[Cc]olumns.Items\[)';
REPLACE_FORMAT = '[[COMP_NAME]]BandedTableView1.Columns[';
var
I: Integer;
Matchs: TMatchCollection;
Match: TMatch;
Comp, Src, Dest: string;
TagStartIdx, TagEndIdx: Integer;
begin
Result := False;
if not (ASrc.Contains('.Columns[') or ASrc.Contains('.columns[')
or ASrc.Contains('.Columns.Items[') or ASrc.Contains('.Columns.items[')
or ASrc.Contains('.columns.Items[') or ASrc.Contains('.columns.items[')
) then
Exit;
Matchs := TRegEx.Matches(ASrc, SEARCH_PATTERN, [roIgnoreCase]);
if Matchs.Count = 0 then
Exit;
ADest := ASrc;
for I := 0 to Matchs.Count - 1 do
begin
Match := Matchs[I];
Src := Match.Value;
Comp := TRegEx.Match(Src, '[a-zA-Z\d\_]+\.').Value.Replace('.', '').Trim;
Dest := REPLACE_FORMAT;
Dest := Dest.Replace('[[COMP_NAME]]', Comp);
ADest := ADest.Replace(Src, Dest);
Result := True;
end;
end;
function TColumnsConverter.ConvertColumnsReadOnly(ASrc: string;
var ADest: string): Boolean;
const
SEARCH_PATTERN = '\]\.[Rr]ead[Oo]nly';
REPLACE_FORMAT = '].Properties.ReadOnly';
var
I: Integer;
Matchs: TMatchCollection;
Match: TMatch;
Src, Dest: string;
TagStartIdx, TagEndIdx: Integer;
begin
Result := False;
if (not ADest.Contains('Columns[')) and (not ADest.Contains('columns[')) then
Exit;
Matchs := TRegEx.Matches(ASrc, SEARCH_PATTERN, [roIgnoreCase]);
if Matchs.Count = 0 then
Exit;
ADest := ASrc;
for I := 0 to Matchs.Count - 1 do
begin
Match := Matchs[I];
Src := Match.Value;
Dest := REPLACE_FORMAT;
ADest := ADest.Replace(Src, Dest);
Result := True;
end;
end;
위와 같이 간단한 1줄을 변경하기 위해 꽤 긴줄의 코드를 작성해야 합니다.
하지만, 한번 작성해 놓으면 해당 패턴이 1번이건 100번이건 또는 다시 작업하더라도 부담이 없어집니다.
이와 같이 소스코드를 분석해 패턴을 찾고, 그 패턴을 처리하는 코드를 추가하는 작업을 지속적으로 반복해야 합니다.
두 도구 모두, 지정된 경로에서 파일을 불러오면 좌측 목록에 표시됩니다. 우측 목록에는 컨버터 목록이 표시됩니다.
컨버터
컨버터란 실질적인 변환작업을 하는 기능으로, 다양한 마이그레이션 코드를 기능별로 분리하고, 구현을 분리하는 역할을 합니다.
마이그레이션 도구를 이용한다면, 기존 컨버터에 기능을 추가하거나, 컨버터 클래스를 상속받아 새로운 컨버터를 개발해 추가할 수 있습니다.
두가지 도구 모두 TConverterManager와 TConverter 클래스 제공합니다. TConverter 클래스는 상속받아 변환작업을 구현하는 베이스 클래스 역할을 합니다. TConverterManager에 컨버터를 등록하면 우측 목록에 컨버터가 추가되어 선택해 해당 작업을 수행할 수 있습니다.
컴포넌트 변환 도구(CompMigrationTool)
컴포넌트 변환 도구의 목적은 폼파일과 소스파일의 컴포넌트 정보를 찾아 변경하는 것입니다.
주요 작업 순서는 다음과 같습니다.
폼파일에서 컴포넌트 문자열을 찾아 추출합니다.
컴포넌트 문자열을 분석해 속성, 이벤트 등의 정보를 추출합니다.
추출한 컴포넌트 정보로 새로운 컴포넌트 정보를 작성합니다.
기존 컴포넌트 문자열에 새로운 컴포넌트 정보를 덮어씁니다.
가장 핵심이 되는 작업은 2번 단계의 컴포넌트 문자열을 분석하는 파서와 새로운 컴포넌트 정보를 작성하는 치환 작업입니다.
컴포넌트 문자열을 분석하는 파서
TObjectTextParser 클래스를 통해 문자열 분석 작업을 합니다. 이 클래스는 델파이에 내장된 System.Classes.ObjectTextToBinary 함수를 참고해 작성되었습니다.
다음과 같이 문자열 분석(Parse) 후 속성정보(Properties.Values[])를 읽어 오도록 구현되었습니다.
리얼그리드를 분석하는 작업은 컬럼, 밴드 등의 복잡한 구조를 분석하기 위해 TObjectTextParser를 상속해 TRealGridParser 클래스를 작성했습니다.
새로운 컴포넌트 정보를 작성하는 치환 작업
파서를 통해 분석한 정보로 새로운 컴포넌트 정보를 작성하는 작업은 미리 컴포넌트 문자열 템플릿을 만들고 필요한 값을 치환하도록 진행했습니다.
다음과 같이 퀀텀그리드(TcxGrid) 컴포넌트 문자열 템플릿 상수를 정의 했습니다.
다음(RealGridToCXGridConverter.GetConvertedCompText)과 같이 파서로 분석한 정보로 치환합니다.
컨버터를 통핸 작업 분리
컴포넌트 정보를 분석하고, 재작성하는 코드는 컴포넌트마다 다르게 작성해야 합니다.
컨버터 클래스(TConverter)를 상속받아 컴포넌트 변환 작업 별 컨버터를 재작성 합니다.
컨버터 클래스의 protected의 virtual 메소드를 전환 대상에 맞게 필요한 메소드를 재 구현해야합니다.
상속받아 재구현 후 파일의 끝 initialization에 TConverterManager에 등록하는 코드를 추가하면, 메인화면의 컨버터 영역에 해당 컨버터가 표시되어 사용할 수 있습니다.
소스파일 변환 도구(SrcMigrationTool)
소스파일 변환 도구의 목적은 소스파일을 읽어 변환할 대상인지 판단 후 변환하는 것입니다.
주요 작업순서는 다음과 같습니다.
소스파일을 로딩 후 컴포넌트를 사용하는 소스파일인지 판단합니다.
구현부(implementation) 부터 소스코드 라인별로 변환처리를 반복합니다.
정규표현식으로 해당 소스코드 라인이 변환 대상인지 판단합니다.
정규표현식으로 찾은 정보(컴포넌트 이름 등)를 이용 변환할 문자열을 작성합니다.
정규표현식으로 찾은 문자열 부분을 변환한 문자열로 치환합니다.
변환된 소스코드 라인을 기존 라인에 덮어씁니다.
가장 핵심이 되는 기능은 정규표현식으로 변환대상 판단 후 변환한 문자열로 치환하는 작업입니다
정규표현식
소스파일 변환 도구는 정규표현식을 이용해 변환 대상을 판단합니다. 다양한 방식으로 작성된 소스코드는 단순한 패턴으로 검색 및 치환 시 의도치 않은 내용이 변경될 수 있습니다. 정규표현식을 이용 정교한 검색이 필요합니다.
다음은 리얼그리드의 "GridName.Cells[].As***" 속성을 사용하는 구문 예시(패턴)입니다.
RealGrid1.Cells[00,RealGrid1.Row].AsString := FieldByName('품번' ).AsString; Cells[숫자, 변수] RealGrid3.Cells[28,Row].AsFloat := FieldByName('부가세단가').AsFloat; 좌항 := 우항 RealGrid2.Cells[45,Row].AsFloat := FieldByName('부가세단가').AsFloat*RealGrid1.Cells[21,Row].AsFloat; 우항, 함수내 SQLText2 := StringSQLReplace('S',SQLText2,'품번', RealGrid1.Cells[62,Row].AsString); 조건식 내 if (RealGrid1.RowCount > 0) and (SW_Func_CharDelete(RealGrid1.Cells[02, RealGrid1.RowCount-1].AsString,'-') = '') then Begin 함수 내, 복수 SQLText := StringSQLReplace('S',SQLText,'반출판매구분' ,Copy(RealGrid1.Cells[24,i].AsString,1,Pos(':',RealGrid1.Cells[24,i].AsString)-1)); Cells[속성, 속성] (RealGrid1.Cells[AColumn.Index, RealGrid1.Row].Value <> Null) then with 문 사용 Cells[3,J].AsFloat := Default_DBBandedTableView.DataController.Values[I,3] - Cells[1,J].AsFloat;
위 예시를 분석하면 다음 특징을 찾을 수 있습니다.
그리드 이름이 다를 수 있습니다.
한 문장에 구문이 여러번 포함될 수 있습니다.
구문은 좌항에 위치할 수도 우항에 위치할 수도 있습니다.
with문으로 그리드 이름이 생략될 수도 있습니다.
위와 같은 다양한 조건에서 원하는 패턴의 문자열을 정규표현식으로 찾아야 합니다.
다음은 위 구문에서 "GridName.Cells[].As***"을 찾는 정규표현식입니다.(정규표현식에 대한 설명은 생략합니다.)
(옵션) 등록 후 구글측의 확인 절차가 필요합니다. 그 전까지 다음 화면과 같이 표시됩니다. 확인 전까지 "고급 > 앱으로 이동"을 선택 해 진행합니다.
구글 드라이브와 구글 시트 권한 요청 화면, 선택사항 확인 화면에서 권한을 허용합니다.
권한을 허용하면 성공 화면이 표시됩니다.
위 과정은 개발 시 구글 시트에 연결할때도 표시되지만, 최종 사용자가 구글 시트와 연결 시에도 동일한 절차로 진행됩니다.
정보 조회/입력/수정/삭제
다음과 같이 폼을 만들었습니다.(버튼 아래의 상자는 TListBox 입니다.)
테이블 목록과 필드 목록 조회
구글 시트에는 여러개의 문서를 문서별 여러개의 시트를 제공합니다. 엔터프라이즈 커넥터에서 테이블은 각 문서의 시트를 대상으로 합니다.
다음 코드를 통해 테이블(문서의 시트) 목록을 가져옵니다. 지정된 테이블의 필드 목록도 가져옵니다.
procedure TForm1.Button2Click(Sender: TObject);
begin
FDConnection1.GetTableNames('CDATA', '', '', ListBox1.Items);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
if ListBox1.ItemIndex > -1 then
FDConnection1.GetFieldNames('CDATA', '', ListBox1.Items[ListBox1.ItemIndex], '', ListBox2.Items);
end;
결과는 다음과 같습니다.
참고> 위 테이블 리스트 등을 가져오는 작업은 문서와 문서의 시트가 많다면 시간이 오래걸릴 수 있습니다.
위 기능은 테이블 목록을 가져와 표시하는 기능으로 실제 구현 시에는 테이블(문서의 시트)를 직접 확인해 SQL 문을 만드는 등으로 활용하시기 바랍니다.
SQL 열기 및 수행
테이블과 필드 정보를 확인할 수 있습니다.
시트의 정보를 TFDQuery 컴포넌트를 이용 표준 SQL 문장을 이용해 조회 및 편집할 수 있습니다.
다음은 SELECT 문과, INSERT 문을 수행한 결과입니다.
새로운 시트 추가
새로운 시트 추가등의 추가 기능은 TFDStoredProc 컴포넌트를 이용할 수 있습니다.
다음은 서비스에 정의된 기능 목록입니다.
새로운 시트 추가는 CreateSpreadseet 스토어드 프로시저를 이용합니다. 코드는 다음과 같습니다.
procedure TForm1.Button4Click(Sender: TObject);
var
Title: string;
begin
Title := InputBox('Create spread sheet', 'Spread sheet name', '');
if Title <> '' then
begin
FDStoredProc1.ParamByName('Title').Value := Title;
FDStoredProc1.ParamByName('Description').Value := 'Created with FDEC';
FDStoredProc1.ExecProc;
end;
end;
배포
개발이 완료되었다면, 라이선스를 포함해 배포 해야합니다.
라이선스 등록 없이 배포 시 다음 오류를 발생합니다.
라이선스 배포는 연결 속성의 RTK 항목을 통해 적용할 수 있습니다.
RTK 항목에 입력할 값은 설치 디렉토리에 포함된 deployment_licensing.txt 파일을 참고합니다.
(정식사용자의 경우 설치 시 해당 라이선스 파일이 생성됩니다.)
파일을 열면 설명과 함께 RTK 값이 포함되어 있습니다. 파일의 내용을 복사 해 연결 속성의 RTK 값 설정 후 다시 빌드해 배포할 수 있습니다.
기타
오류 대응
Use Spreadsheet/Folder connection property to avoid 429 and list subset of sheets.
테스트 중 위와 같은 오류가 발생해 원인 파악한 내용을 공유합니다.
문서가 매우 많은 환경에서 목록을 가져오거나 데이터 조회 시 발생했습니다.
429 응답코드는 너무 많은 요청 시 응답합니다. 추측하면 문서 목록등 조회 시 각 문서와 시트 별로 요청을 하는 것으로 예상되며 그 요청 횟수가 지정된 요청횟수 한도를 넘어 오류를 발생한 것으로 보입니다.
구글 API 콘솔에 다음과 같은 항목이 있습니다.(구글 API 콘솔 > API 및 서비스 > OAuth 동의 화면)
해결 방안은 위 한도 늘리기 버튼을 통해 한도를 늘리도록 설정합니다.
실제 서비스 전에는 다양한 테스트를 진행하며, 한도 등의 서비스 설정을 꼼꼼히 해야 할 것 같습니다.
구글은 다음과 같이 발표했습니다. "GCM 서버와 클라이언트 API는 지원중단하며, 2019년 5월 29일에 삭제될 예정입니다. 안정적이고 확장가능한 GCM 인프라 및 많은 새로운 기능을 계승한 Firebase Cloud Messaging(FCM)으로 GCM 애플리케이션을 전환하십시오."
앞으로 파이어몽키 안드로이드 앱에서 푸시 알림 지원을 사용하려면, 구글의 Firebase를 사용해야 합니다. 이 글에서는 Delphi, C++Builder 및 RAD 스튜디오 10.3.1을 사용해 파이어몽키 안드로이드 앱에서 Firebase 푸시 알림 지원하는 과정을 소개합니다.
사전준비 - 안드로이드 푸시 알림 패치
시작에 앞서 IDE의 겟잇 패키지 매니저(Tools > Getit Package Manager...)에서 "Android Push Notification Patch 1.0"을 찾아 설치 버튼을 클릭 해 다운로드 합니다.("Android"로 검색)
최신 로드맵에서 소개했듯이 향후 10.3.2 릴리즈에서 이 지원을 더욱 간소화(RAD 서버의 Android Firebase 푸시 알림 지원 추가) 및 10.4의 Firebase 및 기타 관련 서비스의 전체 통합 지원 제공을 계획하고 있습니다.
Firebase에서 애플리케이션을 활성화하려면, 다음 3단계로 작업을 진행해야 합니다.
1, Firebase 프로젝트를 만들고 파이어몽키 프로젝트를 Google Firebase 콘솔에 등록
2, 파이어몽키 프로젝트를 새로 만들거나 기존의 프로젝트에 RAD 스튜디오 10.3.1 푸시 알림 구성
3, GCM(Google Cloud Messaging) 대신 Firebase를 지원하도록 파이어몽키 프로젝트 변경
- Cloud Firestore 위치는 현재(2019.05) 가장 가까운 asia-northeast1 추천
3, 프로젝트 생성 후 Project Overview 옆의 톱니바퀴 아이콘 클릭 후 [프로젝트 설정] 메뉴를 선택해 프로젝트 설정 화면으로 이동합니다.
4, 안드로이드 아이콘을 클릭하여 "Android 앱에 Firebase 추가 화면"으로 이동합니다.
5, 앱 등록 단계에서 Android 패키지 이름을 지정 후 [앱 등록] 버튼을 클릭합니다.
- 파이어몽키 앱의 기본 패키지 이름은 com.embarcadero.프로젝트이름 입니다.
- 파이어몽키 프로젝트를 FirebaseApp으로 생성 시 com.embarcadero.FirebaseApp으로 생성합니다.
- 프로젝트 옵션(Application > Version Info의 package 항목)에서 패키지 이름 설정 가능합니다.
6, 구성 파일(google-services.json)을 다운로드 합니다.
다운로드 후 [다음] 버튼 클릭 후 이후 단계는 건너 뛰기를 선택합니다.
7, 안드로이드 문자열 리소스 파일(strings.xml)을 편집합니다.
- strings.xml 파일은 겟잇 패키지 매니저에서 다운로드한 파일에 포함되어 있습니다. (C:\Users\Public\Documents\Embarcadero\Studio\20.0\CatalogRepository\AndroidPushNotificationsPatch-1.0\FireBase)
- strings.xml 파일을 파이어몽키 프로젝트 경로에 복사합니다.(예> C:\MyFirebaseApplication 등)
- 복사한 strings.xml 파일을 텍스트 에디터 등으로 열어 다음을 수정합니다.
* google_app_id와 gcm_defaultSenderId, fcm_fallback_notification_channel_label 항목만 두고 나머지 string 태그 삭제
* google_app_id = mobilesdk_app_id
* gcm_defaultSenderId = project_number (6단계에서 다운로드 받은 google-services.json 파일을 참조)
파이어몽키 프로젝트에 푸시 알림 구성
8, 파이어몽키 프로젝트를 생성(또는 기존 프로젝트 오픈) 합니다.
9, Firebase 콘솔에 등록된 프로젝트 이름과 일치하도록 FMX 프로젝트 이름을 변경합니다.
(이 예제에서는 FirebaseApp으로 설정합니다.)
7단계에서 업데이트 한 strings.xml 파일과 동일한 경로에 프로젝트를 저장합니다.(예> C:\MyFirebaseApplication)
저장 후 안드로이드 플랫폼 선택 후 빌드를 실행해 해당 경로에 AndroidManifest.template.xml을 생성합니다. 이 파일은 마지막 단계에서 편집합니다.
10, Firebase 이벤트 로그를 표시하기 위해 TMemo 컴포넌트를 폼에 추가하고, MemoLog로 이름을 변경합니다.
다음 Form의 OnCreate 이벤트를 추가해 푸시 알림 서비스 초기화 및 연결을 위한 코드를 구현합니다.
이 코드는 겟잇 패키지 매니저에 포함되어 있는 Snippets.txt 파일을 참조하기 바랍니다.
Snippets.txt 파일에는 푸시 알림 서비스로 연결을 만드는 프로세스와 이벤트에 대한 코드가 포함되어 있습니다.
푸시 알림 서비스와 연결을 위한 코드를 OnCreate 이벤트에 복사합니다.
Snippets.txt의 1~2줄을 implimentation 아래에 추가합니다.
또한, interface uses 절에 System.PushNotification을 추가합니다.
이 서비스로 Firebase와 연결 후 장치아이디와 장치 토큰을 수신하게 됩니다. 필요한 변수와 이벤트를 private 영역에 선언합니다.
두개의 변수는 장치 아이디와 장치 토큰을 수신하는데 사용합니다.
OnServiceConnectionChange 이벤트는 연결 변경 시 장치 토큰을 얻는데 사용합니다.
OnReceiveNotificationEvent 이벤트는 푸시를 받기 위해 사용됩니다.
Snippets.txt를 참고해 2개의 이벤트를 구현합니다.
GCM 대신 FCM을 지원하도록 프로젝트 변경
11, IDE 오른쪽의 프로젝트 에서 Android > Libraries를 확장하고, 다음의 라이브러리를 각각 마우스 오른쪽 버튼을 이용해 수동으로 해제합니다.
cloud-messaging.dex.jar
google-analytics-v2.dex.jar
google-play로 시작하는 모든 Google Play 라이브러리
12, 프로젝트의 Libraries에 마우스 오른쪽 버튼을 누르고, [Add] 메뉴를 선택 해 Firebase 라이브러리들과 업데이트된 Google Play Services 라이브러리를 추가합니다.
(이 라이브러리 파일들은 겟잇 패키지 매니저에서 다운로드받은 파일들 중 jars 디렉토리에 있습니다.)
13, 다음 Firebase 지원을 위한 AndroidAPI.JNI.Firebase.pas와 업데이트된 FMX.PushNotification.Android.pas 파일을 프로젝트 경로에 복사 후 추가합니다.
(이 파일들은 겟잇 패키지 매니저에서 다운로드 받은 파일 중에 있습니다.)
14, IDE 메인 메뉴 중 Project > Deployment로 배포화면을 표시 후 프로젝트 경로의 strings.xml 파일을 추가합니다.
또한 strings.xml 파일의 Remote Path를 "res\Values\"로 업데이트 후 변경사항을 저장합니다.
15, 마지막 단계로 AndroidManifest.template.xml 파일을 변경합니다.(9단계에서 안드로이드 타겟인 프로젝트를 빌드해 생성)
Snippets.txt 파일 하단의 XML 구문을 복사해 <%receivers%>아래(</application> 태그 바로위)에 붙여넣기 합니다.
Firebase Clound Messaging 전송 테스트
16, FMX 앱을 안드로이드 장비에 배포합니다. 실행 시 앱은 자동으로 Firebase에 등록되고, 로그에 디바이스 토큰이 표시됩니다.
이 장비에 푸시메시지를 전송하려면 Firebase 토큰이 필요합니다. 로그에 표시된 토큰을 복사합니다.
파이어몽키로 안드로이드 앱 개발 시 장치에 접근하는 기능(예, 카메라 이용, 블루투스 이용 등) 개발 시 권한 설정이 필요합니다.
기존에는 Project > Options > Uses Permissions에서 필요한 권한을 설정하는 방식이었지만,
안드로이드 API 최신버전은 런타임 시 권한을 요청하는 매커니즘으로 변경되었습니다.
기존의 권한 모델은 설치 시 전체 권한을 승인하는 방식이었습니다. 새로운 권한 모델은 기능 사용 시 개별 권한을 묻는 방식으로, 사용자는 기능 별 허용 및 거부가 가능해졌습니다.
기존 권한 요청 방식
새로운 권한 요청 방식
새로운 권한 요청 방식은 RAD 스튜디오 10.3 부터 적용되며,
기존에 작성했던 안드로이드 프로젝트는 권한 요청하는 로직을 추가하도록 업데이트 해야 합니다.
안드로이드의 권한 요청 로직
안드로이드 권한 요청은 System.Permissions.pas에 구현된 PermissionsService.RequestPermissions 메소드를 호출하는 것으로 시작합니다.
다음 코드는 델파이 샘플 중 Location(Object Pascal/Mobile Snippets/Location)의 일부입니다.
procedure TLocationForm.swLocationSensorActiveSwitch(Sender: TObject);
begin
{$IFDEF ANDROID}
if swLocationSensorActive.IsChecked then
PermissionsService.RequestPermissions([JStringToString(TJManifest_permission.JavaClass.ACCESS_FINE_LOCATION)],
procedure(const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>)
begin
if (Length(AGrantResults) = 1) and (AGrantResults[0] = TPermissionStatus.Granted) then
{ activate or deactivate the location sensor }
LocationSensor1.Active := True
else
begin
swLocationSensorActive.IsChecked := False;
TDialogService.ShowMessage('Location permission not granted');
end;
end)
else
{$ENDIF}
LocationSensor1.Active := False;
end;
위코드에서는 TLocationsSensor 즉, GPS 연동하는 코드 전에 안드로이드인 경우({$IFDEF ANDROD}) 위치 권한(TJManifest_permission.JavaClass.ACCESS_FINE_LOCATION)을 요청하는 코드가 추가되었습니다.
첫번째 파라메터는 요청하는 권한 문자열 배열, 두번째는 권한 요청 결과를 받아볼 수 있는 메소드, 세번째는 사용자에게 권한에 대한 안내를 할 수 있는 메소드입니다.
델파이 샘플의 AccessCameraApp의 코드에는 권한요청하는 로직이 다음과 같이 구현되어 있습니다.
procedure TAccessCameraAppForm.btnTakePhotoClick(Sender: TObject);
begin
PermissionsService.RequestPermissions(
[FPermissionCamera, FPermissionReadExternalStorage, FPermissionWriteExternalStorage],
TakePicturePermissionRequestResult,
DisplayRationale
)
end;
// Optional rationale display routine to display permission requirement rationale to the user
procedure TAccessCameraAppForm.DisplayRationale(Sender: TObject; const APermissions: TArray<string>; const APostRationaleProc: TProc);
var
I: Integer;
RationaleMsg: string;
begin
for I := 0 to High(APermissions) do
begin
if APermissions[I] = FPermissionCamera then
RationaleMsg := RationaleMsg + 'The app needs to access the camera to take a photo' + SLineBreak + SLineBreak
else if APermissions[I] = FPermissionReadExternalStorage then
RationaleMsg := RationaleMsg + 'The app needs to read a photo file from your device';
end;
// Show an explanation to the user *asynchronously* - don't block this thread waiting for the user's response!
// After the user sees the explanation, invoke the post-rationale routine to request the permissions
TDialogService.ShowMessage(RationaleMsg,
procedure(const AResult: TModalResult)
begin
APostRationaleProc;
end)
end;
procedure TAccessCameraAppForm.TakePicturePermissionRequestResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>);
begin
// 3 permissions involved: CAMERA, READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE
if (Length(AGrantResults) = 3)
and (AGrantResults[0] = TPermissionStatus.Granted)
and (AGrantResults[1] = TPermissionStatus.Granted)
and (AGrantResults[2] = TPermissionStatus.Granted) then
TakePhotoFromCameraAction1.Execute
else
TDialogService.ShowMessage('Cannot take a photo because the required permissions are not all granted')
end;
1) 3가지 권한을 요청합니다.
2) DisplayRationale 메소드에서 필요한 권한에 대해 설명합니다.
3) 요청결과 메소드에서 권한일 모두 허용한 경우 기능을 실행합니다.
위에서 주의할 점은,
권한에 대한 설명 시(DisplayRationale 메소드) 메시지 호출 후 비동기로 APostRationaleProc 메소드를 호출해야 합니다.
그리고, 권한에 대한 설명 메소드는 옵션으로 생략할 수 있습니다.
위 내용을 참고해 권한이 필요한 기능을 개발하는 경우 해당 기능 호출 전 권한을 요청하도록 추가 및 기존 코드를 변경하시기 바랍니다.
해당 기능은 안드로이드 앱 개발 시 적용해야 하는 기능으로, 다른 플랫폼 앱 개발 시 생략할 수 있습니다.
엠바카데로에서 제공하는 내용과 이미 적용된 샘플 앱의 목록은 다음 링크에서 확인할 수 있습니다.