Firestore를 사용하게 되어 메모 겸 요약본을 작성한다.

문서가 꽤 잘되있어 처음 시작은 어렵지 않을듯.


** firestore 지원 DataType

https://firebase.google.com/docs/firestore/manage-data/data-types?authuser=0



** firestore 데이터 가져오기

https://firebase.google.com/docs/firestore/query-data/get-data?authuser=0



** 시작하기 전에 권한이 없다고 나온다면..


권한에 들어가서 저 부분을 true로 바꾸면 된다. (저렇게 하면 읽기 쓰기 허용)








예를 들어 아래와 같은 데이터를 등록했다고 할 때..




database.document("notice/main").get().addOnCompleteListener(task -> {

    if(task.isSuccessful()){

        //성공

        DocumentSnapshot document = task.getResult();

        List list = (List) document.getData().get("list");

        for(int i=0;i<list.size();i++){

            Log.i("TEST", "data["+i+"] > " + list.get(i).toString());

            HashMap map = (HashMap) list.get(i);

            NoticeData data = new NoticeData();

            data.setTitle(map.get("title").toString());

            data.setContent(map.get("content").toString().replace("\\n", "\n"));

            data.setRegDate((Date) map.get("regdate"));

            Log.i("TEST", "["+i+"] > " + (list.get(i) instanceof HashMap) + " / " + (list.get(i).getClass().getName()) + " / " + list.get(i).toString());

        }

    }else{

         //실패

    }

};



한 줄씩 보자면..

** database.document("notice/main").get().addOnCompleteListener(task -> {

- Document를 가져올 것이라서 document(Colloction이름/Document이름).get()을 사용

- task 는 Task<DocumentSnapShot>이다.


** DocumentSnapshot document = task.getResult();

- 해당 문서 내부의 항목을 가져옴. (HashMap<String, Object> 형태


** List list = (List) document.getData().get("list");


- list라는 이름으로 등록된 필드를 가져옴



** HashMap map = (HashMap) list.get(i);


- Object 형식으로 저장한 데이터를 가져올 때는 Map을 사용함.


** data.setContent(map.get("content").toString().replace("\\n", "\n"));

- 개행문자가 그대로 입력되기 때문에 개행문자를 다시 한 번 변환해준다. (\\으로 입력해야 "\" 문자로 인식됨)


** data.setRegDate((Date) map.get("regdate"));

- 서버에 등록된 Timestamp를 바로 Date로 변환하기 위해서는 아래와 같은 코드를 사전에 입력해두어야 한다.

        database = FirebaseFirestore.getInstance();

        FirebaseFirestoreSettings.Builder settings = new FirebaseFirestoreSettings.Builder();

        settings.setTimestampsInSnapshotsEnabled(true);

        database.setFirestoreSettings(settings.build());

        database.document(....).get()....




** 

문서 내부에 list가 아니라 특정 오브젝트의 형태로 만들어져있다면 

toObject 메소드를 사용하여 손쉽게 Custom Object로 변환할 수 있다.


** 문제점

- 26버전 아래의 TextView에 app:autoSizeTextType 관련 속성이 동작 안 하는 오류 발생

- java 파일에서 TextViewCompat을 사용하는 방법으로도 적용되지 않았음.

- xml파일에서 prefix를 android로 하면 동작하나 app으로 하면 동작하지 않았음.



** 해결

- TextView의 height 값에 제한을 준다. (wrap_content는 쓰지 않는 게 좋음. 만약 wrap_content를 쓰는 경우에는 maxLine 등과 함께 사용)

- singleLine 속성 대신 maxLine 속성을 사용한다.

- TextView 대신 android.support.v7.widget.AppCompatTextView를 사용


** 참고

https://stackoverflow.com/questions/44118002/autosizing-of-textview-doesnt-work-android-o

** 방법

그냥 한 파일에 같이 적으면 됨.





이게 뭐라고 나는 고민을 했던 걸까...


** 사용 이유

기존에 테스트 서버와 연결되는 앱과 실제 서비스 서버와 연결되는 앱이 따로 설치되기를 희망하였음.

이번에 마이그레이션 작업 진행하며 기존 앱과 따로 설치하여 테스트하기를 희망함.

(기존 앱은 이클립스로 작업)




** 방법

1. 해당 모듈의 build.gradle에 아래와 같은 형식으로 추가

- 나는 마이그레이션/DEV/SERVICE로 나누었음.


flavorDimensions "mode"

    productFlavors {

        migration {

            dimension "mode"

            applicationIdSuffix ".migration"

            buildConfigField 'boolean', 'isTestMode', "true"

        }

//        dev {

//            dimension "mode"

//            applicationIdSuffix ".dev"

//            buildConfigField 'boolean', 'isTestMode', "true"

//        }


        service {

            dimension "mode"

            applicationIdSuffix ""

            buildConfigField 'boolean', 'isTestMode', "false"

        }

    }


flavorDimensions - 무엇인지는 모르겠지만.. flavor 설정 시 모든 항목이 같은 flavorDimensions에 속해야 한다는 에러메세지가 떠서 추가


migration/dev/service - 그냥 자기가 마음대로 설정 가능. 이후 폴더를 생성할 때 동일한 이름으로 생성해야 한다. (대문자가 되는지는 아직 확인하지는 못했다.) 이 때 Flavor가 하나 이상 등록되어있으면 나중에 APK 뽑을 때 Flavor를 무조건 선택해야되서 service를 추가하였음.


applicationIdSuffix - 식별자를 정하는 것 같음. Suffix 설정 시 applicationId 뒤에 붙여지게 된다. 예를 들어 마이그레이션용 앱의 applicationId는 com.example.flavortest.migration 이 되는 식. 나중에 google-services.json 복사 시 문서 내의 applicationID와 동일하게 적어야 한다. service는 기본 설정과 동일하게 해 주기 위해서 빈 값으로 설정하였다.


buildConfigField - 설정하게 되면 BuildConfig 내부에 static 변수로 등록이 되서 소스 코드 내에서 사용이 가능하다. 예를 들어 migration과 dev의 앱에서 BuildConfig.isTestMode는 true지만 service에서는 false가 된다.




2. 각 flavor 별로 폴더 추가



위에서 만든 flavor별로 폴더를 만들고, 내부에 google-services.json을 복사해서 넣는다.

(이제와서 보니 service 폴더랑 main 안의 google-services.json 파일은 없어도 될듯..)


각 flavor별로 리소스 관리를 할 수 있다.

따로 설정하지 않으면 기본(main)폴더 내의 리소스를 사용하는 듯.



이런식으로 앱 아이콘을 따로하고 싶을 때 사용한다. (우측은 플레이 스토어에 등록된 앱, 좌측은 migration flavor로 설치한 앱)




3. google-services.json 수정


google-services.json 파일은 



이렇게 package_name 부분만 변경해주면 된다.


gradle의 applicationId에 위에서 추가했던 applicationIdSuffix 를 더한 문자열을 쓰면 됨.

예를 들어 migration 폴더의 package_name은 "com.example.flavortest.migration"로 변경하면 됨.






4. Manifest 수정(FileProvider 사용 시)


마지막으로 혹시 FileProvider을 정의해서 사용한다면, 혹은 Manifest 파일에서 android:authorities 속성을 사용한다면 android:authorities 속성의 값을 변경해야 한다.

(이렇게 안 하면 앱이 동시에 설치가 안 된다.)


android:authorities="${applicationId}.fileprovider"



이와 같이 applicationId를 변수로 가져오게 수정하여야 한다. 





5. 빌드 시 flavor 선택하기





Build Variants 메뉴를 통해 Run 및 Instant Run 시 어떤 flavor로 실행할 지 선택할 수 있다.







APK 추출 시에는 Flavors 부분에서 선택할 수 있다.

(그런데 Flavors가 하나 이상 등록되어있을 때 선택 해제가 안 된다. 그래서 service Flavor를 따로 만들었음.)








** 참고

http://gun0912.tistory.com/74

https://stackoverflow.com/questions/16267785/install-shows-error-in-console-install-failed-conflicting-provider 

https://medium.com/@iammert/android-product-flavors-1ef276b2bbc1

항상 최상단에 위치하는 뷰 구현 시 문제가 발생한다.


1. '다른 앱 위에 그리기' 권한을 허용해 준다.


if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){

    Log.i("TEST", "Permission Granted ? " + Settings.canDrawOverlays(context));

    //M 이상에서만 퍼미션 확인(그 이하에서는 자동으로 허용됨)

//다른 앱 위에서 그리기 권한에 대한 허용 여부 체크

    if(Settings.canDrawOverlays(context)) {

    //이미 권한 설정 되어있음

        context.startService(new Intent(context, XXSerivce.class));

    }else{

        //권한 없음        

        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName()));

        context.startActivity(intent);    //startActivityForResult로 대체 가능

    }

}




2. 서비스에서 addView를 할 때 버전에 따라 Flag를 다르게 설정


int LAYOUT_FLAG;

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

    LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

}else{

    LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_PHONE;

}

WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(

  가로 크기,

세로 크기,

LAYOUT_FLAG,

WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,

PixelFormat.TRANSLUCENT);

mWindowManager.addView(표시할 뷰, mParams);




** 참고

https://stackoverflow.com/questions/46208897/android-permission-denied-for-window-type-2038-using-type-application-overlay

+ Recent posts