[Projects] 토이프로젝트[잡학사전with알람] 개발 comment
개발을 시작한일자는 1/25/2022 이지만, 만들기 시작할때부터 comment를 남길생각을 못했다.. 이유는 금방만들줄알앗고 처음아이디어는 간단하게 시작했기때문에..
간단하게 만들줄알앗던것이 기능들을 더추가시키면서 양이많아졋고, 달리다보니 블로그에대한 생각도 잊혀졋다…..
문득 생각이들어 지금부터라도 남겨야겟다. 70프로정도 만들어둔상태지만 = 는 결국그러지못하고 마무리다하고 1차게시하고나서야 작성한다.. 반성하자
아이디어
앱개발을 본격적으로 공부하고싶어지는 마음이들었고(작년6월), Doit안드로이드 앱프로그래밍 책으로 공부를하다가(이게맞나 싶으면서 집중도잘안되다보니 한달공부하고 네달을쉬었다..) , 12월달쯤 인생의 진로에대해 고민하다가 유튜브알고리즘으로 어느영상을보게됬는데 생각보다 안드로이드가 더매력적으로 보였다.
그래서 이거다! 싶어서 다시시작하려고 유튜브를통해 안드로이드공부법을 찾다가보니 책은 너무 old한지식들이라 공식문서를 보면서 공부하라는 조언이담긴 유튜브영상을 보게됬고,
그것을 시작으로 안드로이드의 핫한 클린아키텍쳐를 접하게되었다.
현업에서 많이사용된다는 RecyclerView와 mvvm+AAC 패턴을 사용해서 앱을 만들고자 생각했는데, 생각보다 아이디어가 안떠올랐고, 일단뭐라도 시작해보자는 마음에
내가 어릴때부터 작년까지 즐겨왓던 게임 ‘아이모’ 를하면서 이전에 알람봇을 카톡봇어플을 이용해서 만들었던 기억을 바탕으로 이번에는 내가 알람앱을 만들어보자! 로 시작됬다.
개발1주차
1주차에는 MVVM패턴(Model,ViewModel,View) 에 AAC(Android Architecture Component) 들을 공부하기 시작했다.
열심히 구글링해서 파악한것으로는 MVVM패턴은 View-ViewModel-Model 순으로 View는 UI만, 데이터를가공하는것은 ViewModel이, 데이터를저장하고 관리하는것은 Model이 각각맡아서 UI와 데이터를 분리하여 처리하는 패턴인것같다. (아직공부가 부족해서 책을한권사서 봐야겠다)
AAC에는 LiveData,ViewModel,Room 과같은것들로 MVVM패턴을 구현화하는것으로 판단된다. ( AAC의 ViewModel의 이름이 MVVM의 ViewModel과 같지만 이름만같을뿐 같은게아니라고한다.)
1주차에는 AAC를 이해하고 만드는데 집중했다. 구글 Developers의 공식문서와 codelab을 참고하면서 Room와Repository로 Model을 구성하고, ViewModel과 LiveData를 이용하여 ViewModel을 구성했다.
Room에는 Entity(DB개체이자 테이블),Dao(Query문들을 작성하는곳), Database(실질적으로 DB파일을만들고, 싱글톤으로 DB객체를 생성하여 리턴받는곳) 로 구성됬다.
Room의 Dao를통해 Query하여 얻은 데이터들은 Repository에서 받아와서 ViewModel이 참조했다.(이렇게 굳이 Repository를 만드는이유는 뷰모델과 데이터베이스사이의 의존성을 줄이고 데이터베이스 혹은 네트워크로부터 받은 데이터들을 이곳에서 추상화하기 위해 사용. 구글에서 권장하는 사항이므로 반드시따르기로 했다.)
Dao에서 LiveData로 return 받은 데이터들을 ViewModel에서 받아서 뷰로 넘겨주었다. 그리고 View에서는 Observer를 사용하여 데이터변경이있으면 React하여 변경된데이터를 즉시 반영하도록 구성했다.
이슈발생
이때 첫번째 Issue 발생- 구글Codelab에서 Dao에서 Query하여 데이터베이스로부터 데이터를 리턴받을때 반환인자를 Flow로 wrapping(감싸다)해왔다. 그이유는 flow로 래핑하면 비동기적으로 데이터를 처리하는거 라고 했다. 그런데 희안하게.. flow로 래핑을 두개의 query(select) 리턴인자로 사용했더니
처음데이터를 가져올때는 두개다 가져오더니, 앱에서 홈화면으로 나갓다가 다시들어올때 ‘시스템중지’ 가 발생했다.
이부분에대해 3일정도를 소비했는데, 이유는 찾지못했다. 아무리구글링해도 재대로된지식배경도없이 검색효과가없엇던건가.. 그런데 디버깅을 해보았더니 첫번째 observer는 데이터를 가져오는데 두번째observer에서는 데이터를 못가져오는것이었다.
이유를알수없자, 다른 사람들의 MVVM 사용예제를 보면서 비교해봣더니, flow로 래핑하지않고 ‘LiveData로 가져오는것이었다.
그래서 그렇게시도하자 둘다 데이터를 가져오고 화면밖으로 나갓다가 와도 중지가 되지않았다. ( 이유는 아마 flow에 있는것같으니 이후에 공부해서 원인을 확실히찾자)
개발2주차~3주차
navigation 을이용하여 single activity에 fragment들의 전환으로 구성했다.
single activity는 안드로이드에서 권장(?) 하는 아키텍쳐 패턴으로, 하나의 액티비티와 여러개의 프래그먼트들을 navigation을통해 구성하는 것이다.
구성을위해 액티비티의 xml에 fragmentContainerView를 두고 소스코드파일에서 fragmentContainerView에게 navcontroller의 제어권을 넘겨주면 된다.
그러면 액티비티위에 fragmentContainerView가 올라가고 이녀석이 UI제어권을 가지게된다. 그후 navController를 이용해서 navigation을 구현한다.
사실 이부분에서, drawer를 이용하여 메뉴를넣고 표현하는방식과 홈화면에 recyclerView를 두어 Item의 클릭으로 navigation을 할지 고민했었다.
초기에 구성하기에, 도감,드랍아이템,알람 정도로만 만들어서 아에 게임도감어플로 출시방향을 잡았던터라, 메뉴가 너무적은데 굳이 drawer를 두면 그속이 너무 훤~ 할거같아서 그냥 후자를 선택하고 진행했다.
메뉴구성후 UI는 심플하고 직관적인걸 평소에 선호하던바라, 사실 할수있으면 이쁘고 깔끔한 UI로 구성하고싶엇지만 그럴지식도 시간도 너무부족했기에(곧 개강을 앞둔터라)
UI에 시간구성을 들이는것도 물론 필요하지만, 기능구현에 초점을두고 UI는 차차 업데이트를 하자는 생각으로 심플하고 직관적으로 구성하기로 했다.
데이터베이스 구성
UI구성후에는 데이터베이스 구축에 시간을 많이들였다. 데이터베이스를 여태 공부한적도없엇고, 3학년 커리큘럼에 포함되있엇던 터라, (현재2학년이끝나고 군복무후 복학을앞둠)
전혀지식이없어서 개인적으로 책을사서 공부하느라 데이터베이스공부만 1주일반정도? 들인것같다.
이미 드랍아이템은 커뮤니티의 활성화로 많은정보가 공유되있었고, 도감은 게임내에서 전체리스트를 보여주기때문에 전부다 테이블을 만들고 수기로 타이핑했다.
타이핑하는데 시간이 꽤나걸렷다 24시간정도?? 게다가 데이터를 입력후에도 수정하거나 추가해야할것들이 자꾸나생겨서 더 시간이 걸린것같다.
요구사항분석후 개념적설계로 E-R다이아그램을 만들었는데, 도감의경우 기본키로 id값, 스텟,재료 이렇게 3가지의 속성값이 필요했다.
처음설계때는, 스텟의경우 여러개의스텟(체력,마나,체력재생,마나재생,힘,지능.민첩 등등.,,) 의값이필요하고 재료역시 하나만있는게아니라 하나의id값에 여러개의재료가필요했기때문에
스텟과 재료 모두에 대해서 다중값으로 두고 설계했다. 책에서 다중값은 논리적설계때 relation을 새로만들고 PK를 다중값속성들과 부모의PK를FK로 둔것으로 한다고 배웠다.
그렇게하고보니 문제점이보였다.
이슈발생
두번째 issue 발생- 재료의경우 다중값임이 명백하지만, 스텟의경우는 각각의 스텟값이 0(없거나) 존재하거나 둘중하나고, 하나의id값에대해서 각각의스텟값들이 존재하는거였다.
즉 스텟에는 체력,마나,체력재생,마나재생 등등.. 이 포함되지만 그값들이 스텟 이라는 다중값이아니라 각각의 다른 속성값으로 봐야했다.
게다가, 재료부분은 재료값만 잇는게아니라 각각의재료가 여러개일수있기때문에 재료에대한 개수 속성도 필요했다. 그래서 이부분을 다중값으로 두는게아니라,
도감에는 여러개의 재료들이 표현된다. 라는 요구사항분석문으로 수정하고, 도감과 재료를 표현하다(가지다)의 일대다 관계형식으로 정의했다.( 도감에는 여러재료가필요하지만, 재료는 도감하나만을 필요로함)
더불어 세번째issue- ROOM 라이브러리에서는 여러table의 join을 하는 Query문에대해서 매핑구조를 지원한다.
매핑구조란, Map<key,value> 값으로 쿼리문의 from의 테이블은 key로, join의 테이블은 value로 감싸서 각각 key-value형태로 매핑시켜서 반환해주는 방식이다.
근데 스텟과 아이템을 둘다 다중값으로 설정하고 도감테이블에서 조인을해서 가져오면 key는 도감테이블이 되지만, value에 두개의값을 넣을수가 없었다.
이부분은 더알아봐야겟지만, 자바에서는 arraylist에 두개의 타입을 넣어서 2차원배열형식을 만들수잇는데, 코틀린에서는 arraylist가 한가지타입만을 받도록 되있어서
어떻게하는지 알수가없엇다. 분명조인을해서 매핑을해서 데이터를 가져와야하는데 공식문서에도 2개의테이블을 매핑시켜주는 예제만잇지 3개이상의테이블은 찾아볼수가없었다.
이러한 두가지의 issue를 모두 해결하는방법으로 도감테이블안에 id값과 각각의여러스텟값들을 구성하고, 재료테이블에 도감의PK를 FK로, 자신의id값과 그에 매칭되는 재료값들 과 개수로 구성했다.
도감구성후 드랍템 테이블은 같은방식으로 구성했다.
드랍템은 몬스터와 몬스터가존재하는 맵으로 구분한다. 몬스터에는 이름,레벨,젠타임 등등의값과 맵에는 맵이름으로 구성된다.
몬스터의경우 각각의 몬스터가 드랍하는 아이템이 다중값이었다. 따라서 이를 다중값으로 두고 새로운 relation을 구성하여 아이템과 몬스터의PK를 가져와 그모두를 PK로 구성했어야했다.
그런데, 도감이랑 헷갈렷던것인지 왜 아이템과 몬스터를 일대다 관계로 정의한거지??.. 이부분은 수정해야겠다.
맵의경우 각몬스터는 여러맵에 존재할수잇고, 각맵에는 여러몬스터가잇으므로 다대다 관계이다. 따라서 새로운relation (관계테이블)을 구성하고 몬스터와 맵의 pk를 각각 가져와 FK로 구성하고 자신의 id값을 PK로 구성하면 된다.
하지만 이역시나, 실수를했다. 몬스터를 FK로 두고 몬스터에대응하는 맵이름속성값을 두어 일대다 형식으로 구성했다…. 수정해야겠다.
이 두가지의 실수는 시간에쫒기며 너무서두른탓인가.. 만들때는 생각을전혀못햇는데 앱게시후 전체적으로 리빌드를 해보니 보인다.. 물론 동작상 오류가발생하는건 아니지만, 이러한설계는 매우잘못된설계임을 분명히 되뇌이고, 잊지말아야겠다.
Filtering 구현
이 두가지의 데이터베이스 설계후 UI에서는 드랍템 메뉴를 선택하면, 맵들이 보이고 , 각각의 맵을 선택하면 해당맵의 몬스터들이 표현된다.
물론, 메뉴구성은 모두 recyclerView 이고 각 recyclerView의 adapter에 onItemClickListener 인터페이스를 override하여 아이템클릭리스너를 구현하여 동작시 navigate 하도록 구현했다.
또한, 도감과 드랍아이템에서 툴바를 actionBar로 만들어서 옵션메뉴(검색)를 구성했다. 검색메뉴 클릭후 문자열이 입력되면 입력된문자에대해 filter하여 recyclerView의 아이템을 재생성한다.
이때 많은 시행착오가 있었는데, 일단 optionmenu에 searchView를 구성하여 setOnQueryTextListener 를 override해서 searchView의 텍스트변경 리스너를 등록했다.
이후 adapter에 filterable 인터페이스를 implement 해서 filter 객체를 생성하여 searchView에 들어온 input문자열에 대해서 입력전,입력중,입력후 로 나누어 처리한다.
입력이 들어오면 performfiltering() 에서 입력문자가 하나하나들어올때마다 처리하여 FilterResult객체를 반환하고, 반환된 FilterResult객체를 publicResult() 에서 받아서 변경된데이터로 수정하고 notifydatasetChanged()로 갱신한다.
-
입력전인 즉 아무입력문자가 없을때는 전체데이터를 입력전이라는 변수에 할당하고, 입력후 변수에 모두 할당한다. 이렇게하면 searchView에 문자를 입력했다가 모두지웟을때 전체데이터를 다시불러와서 원래상태를 보장해준다.
-
입력이들어오면 입력된 문자를 포함하는 아이템들을 입력중 변수에 모두 할당하여 입력후 변수에 모두 할당한다. 그런뒤 입력후 변수값들을 FilterResult객체에 담아 반환하면, publicResult() 라는 메소드에서 FilterResult객체를 받아 recyclerView의 items리스트에 할당하고 갱신하여 들어온 문자열에 해당하는 아이템들로 변경하여 보여주는 방식이다.
여기서는 큰 이슈들은 딱히없엇고, 그냥 구성을 이해하는데 좀힘들었다. 옵션메뉴에 searchview를 등록하고, 서치뷰에 들어온입력문자열에대한 리스너와 그때마다 필터링하여 리사이클러뷰를 갱신하는부분. 특히 필터링부분은 관련예제 3~4개를 보며 구현했다. 구현하고나서는 별거아닌거로 보이기는한다… 역시 처음이어려운가?
개발 4주차~ 앱1차게시
4주차의 주된목적은 알람기능이다. 사실 가장처음의 목적은 firebase를 연동해서 host client가 보스타이머를 등록하면 host에 해당하는 client들에게도 함께 타이머 알람메세지를 출력되게끔 구현하고싶었다.
일단 1차적으로는 개별client들이 개별적으로 알람을 설정할수있도록 구현했지만, 차후 업데이트를통해서 firebase연동후 로그인기능을넣어 위기능을 구현해보고싶다.
spinner를 통해 몬스터 타입단위로 구분하고(첫번째스피너) 구분된 타입에해당하는 몬스터들을 표현했다.(두번째스피너) 그리고 몬스터가 죽은시간을 입력하는 dialog를 호출하는 버튼과 알람을 설정(setAlarm)하는 버튼, 마지막으로 진행중인 알람을 표현해주는 recyclerView로 UI를 구성했다.
알람기능에서는 alarmManager를 통해 알람을 등록하면, pendingIntent로 getBroadCast를하여 triggertime이 되면 broadCast의 onReceive()가 호출된다.
그러면 onReceive에서 notification을 만들어서 보내준후에 startService()로 서비스를 실행하여 현재 진행중인 알람내역을 보여주는 recyclerView 에서 사라지게끔 구현했다.
먼저, 알람에대한 데이터를 alarmModel에서 다루도록하고, View에서 dialog를통해 받은 보스가죽은 시간과 분의 값을 전달받아, alarmManger내부에 setAlarm()과 clearAlarm()를 구현했다.
여기서 고민이 좀많았는데, 이걸굳이 androidViewModel을 상속한 뷰모델클래스를 만드느냐 아니면 알람데이터를 관리하는 알람아이템 데이터클래스를 만드느냐 를 정말많이고민했다.
결국에는 둘다만들어서 알람데이터클래스 객체를 뷰모델의 setAlarm에 전달하여 데이터를 객체화해서 보내는 방식으로쓰고 (데이터를 하나하나받아 전달하면 관리하기힘들기도하고, 데이터자체가 데이터베이스에있는 데이터를 쿼리를통해 받아서 처리하므로 객체화된데이터라서 바로담아서쓰면 편하기도했다. )
뷰모델에서는 들어온데이터들에 대한 처리를 하는방식으로 사용했다. 진정이것이 MVVM이 맞는지 내가사용한것이 정답인지,, 아닌지 솔직히 잘모르겠다. 이부분은역시 MVVM관련 책을사서 되짚어봐야 할것같다. 그러나 뷰와 데이터를 나누는방식으로 사용햇고 최대한 MVVM에 이해하고 사용하려고 노력했다.
알람을 등록할때는 입력된 컷타임에 젠타임을 더하고 현재시간을 뺀만큼을 triggertime으로 두었다.
알람을 등록하는것도 또한역시 굳이 브로드캐스트리시버와 서비스를 둘다두지않고, 모두를 서비스에 구현하는방법도 나중에 문득들긴했는데 솔직히 뭐가 옳은 아키텍쳐사용방식인지 구분이안갔다. 이부분또한역시 공식문서를 정독해서 이둘의 관계를 따로 포스팅할 생각이다. (주요한 4대컴포넌트중 하나니까)
이슈발생
broadCast Receiver를 통해 notification을 생성하는것을 알람구현예제들을 보고 구성해봤는데, 리사이클러뷰를 갱신하는건 뷰에서 일어나야하는 부분이었다.
이부분을 구현하는데 꽤나 힘들었다. 이때까지만해도 service클래스를 구성하여 백그라운드작업을 해야한다는점을 인지하지 못한상태였다.
처음에는 broadcastreceiver에서 알람뷰모델을 선언하려했는데 되지를않았다. 이유는간단하다 뷰모델은 물론 자체생명주기가 존재하지만 애초에 뷰모델은 뷰에서의 데이터변경사항이 감지되면 옵저버가 뷰모델에 데이터변경사항을 알려주는 방식으로 설계되었기때문에 뷰모델은 activity와 fragment에서만 사용이 가능하다.
그러면어떻게 해야하지? 라는의문점을 가지고 찾아보다보니 백그라운드인 service에서 뷰모델이아닌 모델에 직접접근하여 데이터를 수정하면 되는거였다.
즉, 현재진행중인 알람들은 알람데이터베이스에 statue로 구분하여 0이면 off,1이면 on 으로 구성해서 나타내고 데이터베이스내용을 observer를 등록하여 뷰에보여주면되며
데이터베이스수정은 백그라운드인 service에서 데이터베이스의 repository에 접근해서 update 쿼리문을 호출해주면 되는거였다.
그런데 여기서 또 중요한것이 Dao에서 update 쿼리문을 suspand 로 작성하기때문에,service 에서 코루틴스쿠프로 쿼리문을 호출해야했다.
아직 코루틴에대해 정독을 못했던터라 코루틴의 생명주기를 처리할 자신이없어서 방법을 찾던중에, LifecycleService 라는 클래스를 찾았고 service대신 이를 사용해서
코루틴생명주기를 대신처리해줄 lifecyclescope 로 쿼리문을 IO스레드로 호출하여 처리했다.
마지막으로 에뮬레이터 테스트를끝내고 나의 갤럭시s20으로 테스트를 진행했는데, 또 오류가 발생했다.
알람을 설정하자, Caused by: java.lang.IllegalArgumentException: com.jinproject.twomillustratedbook: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.
이런메세지가 뜨면서 강제종료가 되는거였다. 찾아보니 안드로이드12부터 발생하는 최근업데이트로인해서 알람플래그에 FLAG_UPDATE_CURRENT를 사용하지못하는 거였다.
이유는 잘모르겠으나 신버전부터 이를 deprecate 하는거같다. 그래서 stackoverflow에 찾아보니 위 오류문구에 명시한것처럼 flag_immutable로 설정하라고 했다.
그래서 flag_update_current or flag_immutable로 하니 정상적으로 작동은 했다. 하지만 또다른 이슈가 발생했다.
분명히 triggertime을 10초나 20초와같이 직접지정해주면 재대로동작하는데, 이상하게 계산된 젠타임count값을 넣으면 알람이 지정된 시간에 울리질않았다.
그래서 count값을 log로 찍어봣는데 정상적인 값이었다. 젠타임계산값도 틀리지않았는데 왜 알람이 지정된시간에울리질않지? 이상하게생각했고 분명이 에뮬레이터에는 재대로동작을하는데 내폰에서만 안되면 이건분명히 시간값을 dialog에서 받을때 뭔가 잘못되는건가? 싶은생각이들었지만 dialog에서 input된값이틀리면 count값도 틀려야한다. 하지만 정상이다.
깊은생각으로 에뮬레이터와 내휴대폰의 차이점이 뭐지를 생각하다가 에뮬레이터는 미국시간이라 오전시간대엿고 나는 오후시간대라 내휴대폰은 오후값이라는점이 뭔가 다르다고 문득 생각이들었고, 코드에서 simpledataformat으로 시간과 분값을 받는데 이부분을 구글링해보니 역시나.. simpledataformat의 hh는 0-11의 값만 처리하고 HH는 0-23의 값으로 24시계시를 사용하는거였다. 이부분을 HH로 수정하니 정상작동함을 확인했다.
마지막테스트로 화면이꺼져잇을때 정상작동하는가를 확인하고싶었다. 테스트를해보니 안된다… 분명히 alarmManager의 setExactAndAllowWhileIdle()를 사용하면 도즈모드에서도 알람이 정확한시간에 울리도록 보장한다고 했는데, 이상하게도 15분짜리 타이머와 30분짜리타이머를 같이등록했을때 둘다 울리지않아서 화면을 키니까 두알람이 모두한번에 울렸다.
딱 드는생각이 도즈모드에서 계산은 정확하게 됬는데, 화면이꺼져잇으니 notification자체가 만들어지지않는것같았다. 즉 wakelock이 보장되지않는것이다. (이부분은 예전에 카톡봇R을 이용해서 보스타이머를 쓰고있을때 접햇던문제라 익히파악하고있었다)
wakelock이 안되니.. 다시 구글링을해서 찾아보니 setAlarmClock() 은 그모두를 보장해준다는걸 알게됬고 변경해서 정상작동함을 확인했다.
이후 앱게시를 하느라 또 하루를 소비했다. 앱게시또한 어렵다.. 광고도넣어야하고.. 근데 가장 이상했던점은 개발자등록을하는데 25불을 내야된다….. 납득이안된다..
번외로 역시 독점이란건 무섭구나..햇는데 옆동네 애플에서는 매년100불을 내야한다길래 안드로이드선택하길 잘했다고 생각이들었다.. 구글짱
끝으로
사실 일일코멘트로 작성했어야 정확한 이슈발생후 원인에대처한 결과들을 상세하게 적을수있다. 하지만 하루종일 공부공부공부하고 저녁밥을먹고나서는 너무지쳐서 그럴힘이 없었다….
아마 몇몇은 기억하지못하고 빠진부분들도 잇을거라생각한다. 작성하면서 느끼는것이 미리미리 하자 이다… 또한 부족한부분을 깊게파고들지못하고 얕게알고 쓰고 넘어간부분들
이를테면 MVVM, 코루틴,서비스,브로드캐스트 등등은 꼭 공식문서 정독하고 이해못하면 책을사서 이해하도록 노력해봐야겠다.
물론 일일코멘트를 남기는 습관을 들여야 한다. 이전에 유튜브에서 비전공자인 배민개발자 이셧던분의 인터뷰를보니 일일코멘트가 중요하다고했다.. 앞으로는 꼭남기기로 결심해본다.
업데이트
1.0~1.2
- 앱에 광고 unitID를 test값으로 등록을했다가 수정후 재등록
- 알람을 정확한시간동작 에서 5분전 동작으로 변경후 재등록
1.3
- 도감-코스튬 이 추가
- 기존 도감버튼클릭시 바로 전체도감 출력 에서 도감분류fragment 추가후 분류View에서 잡탬류,무기류,코스튬류 를 선택하여 볼수있도록 분류View 추가
- 알람부분 UI구성을 좀더 보기좋게 정렬함
1.4
- 알람부분에서 보스타입 spinner 선택후 타입에따른 몬스터들을 나타내는 spinner 에서 보스타입이 named는 정상동작하지만, boss와 bigboss선택시 named 보스를 알람설정됨
–> 원인: 타입을나누는spinner 변경시의 listener에서 타입에따른 몬스터들을 observer로 가져와서 arraylist에 add하는데, 그렇게햇을때 타입이 변경되도 기존의 add된 몬스터들이 남은상태로 그뒤에 add되는 형태엿엇음.
–> 해결: listener 시작시 리스트를 clear() 하고 변경된 타입에따른 몬스터들을 add함
- 데이터베이스내용에 일부 미지원하는 몬스터들에대해 타입을 변경해서 spinner에 나오지않도록 변경함
—> 문제발생: 데이터베이스 내용이 변경됫을때 해당변경점이 반영이 안됨
—> 원인: 기존에 데이터베이스 스키마변경시 automigration 을통해서 데이터베이스 이전을 했는데, 그때 gradle.app에서 스키마를 export하고 스키마의변경점이 없을때 데이터가변경되고 데이터베이스 버전을 올린다해도 변경점이없어서 기존의 스키마를 그대로 이용함
—> 해결: automigration 제거후 manual migration을 구현함. manual migration은 Migration.migrate() 메소드를 재정의하여 databaseBuilder.addMigrations() 에파라미터로 넣어 구현함
- 일부기기에서 드랍아이템부분이 짤리는 이슈발생
—> 해결: 크기조정
1.5.0~1.5.1
- 드랍몹(대형보스급)빅마마,우크파나,바슬라프,일루스트,마녀딜린 추가
—> 데이터베이스 내용수정(type에 bigboss로 변동)
- 알람화면 UI구성 추가 (화면중앙에 젠타임표기&제거&전체ON/OFF)
—> 버튼3개 추가구성 (특정보스 선택하여 그보스의 타이머가 등록됬을때 화면상단중앙에 표시해주고, 타이머가 울리고종료되면 함께 사라짐)
—> 구성방법: 새로운 Service클래스파일 생성하여, onCreate()에서는 화면위에띄우는방법인 window overlay 생성함 windowManager의 LayoutParams 메소드로 길이와높이를 정의하고, TYPE_APPLICATION_OVERLAY 타입속성을 넣어 상태바나 IME의 아래이지만 외의 모든액티비티 윈도우위에 즉, 다른앱위에 표시해주는 속성이다. 그리고 Flag_Not_Focusable을 통해 클릭을해도 포커싱받지않아 키패드가 뜨지않도록 구성했다. 또한 Format부분에 PixelFormat.TRANSLUCENT을 두어 불투명 포맷을 정의했다. 이후 화면위에그릴 레이아웃 xml을 정의하고 inflater를통해 inflate한뒤 windowManger 객체에 addView()를통해 뷰를 추가함으로써 구성했다.
화면위에그리는 window의 Overlay는 권한이 반드시 필요하다. 따라서 service를 호출하기 직전에 버튼들이 클릭했을때 권한을 요청하게끔햇고, 권한이 수락되지않으면 기능이 동작하지 않도록 구성했다. 내앱은 마시멜로우(안드로이드6.0)이상만 지원하기때문에 마시멜로우이상의 API버전에서 Settings.canDrawOverlays()를 호출하여 패키지이름을 주소로 setting.Action_manage_overlay_permission 코드를넣은 인텐트객체를 보내어 앱권한을등록하는 setting이 열어지도록 했다.
이후 권한이 수락되면, 해당버튼3가지(등록,삭제,전체on/off)을통해 overlay를 이용할수있도록 구성했다.
- 날짜단위 추가 및 24시 넘어갓던버그해결
—> 간단하게 타이머를 등록할때 시간부분이24시가 넘어가면 day에 1을추가하고 시간에서24를빼도록 했다. 추가적으로 day(요일)을 표시하도록 데이터베이스 스키마를 변경하고 DAO를 수정했다.
- 현재진행중인 알람내역 요일-시-분-초 순으로 정렬기능추가
—> 타이머 데이터베이스를 observer로 받는부분에서 adapter로 데이터를넘기고 adapter내부에서 넘어온 리스트데이터를 Collections.Sort() 를 override 하여 Timer Entity클래스에 Parcel을 정의하고 comparator에 넣어 일-시-분-초 순으로 정렬하도록 구성했다.
- 특정기기의 UI 가로넓이 짤리는부분 개선
—> 해결: 이전버전에서도 수정한다고 했었는데 반영이 안됬었다. 이유는 RecyclerView Adapter에서 아이템뷰를 binding할때 DropBinding 형으로 뷰바인딩을 해야하는데 BookBinding형으로 선언해둬서 그랬었다.
따라서 그부분은 수정했고, 또한 부모컨테이너가 ConstraintLayout인 xml에서 하위 뷰의 app:상하좌우의 위치를 결정하는속성(layout_constraintTop_toTopOf와같은속성들) 중에 위와 좌(start)부분만정의하고 아래와 우측(end)부분은 정의를하지않아 constraintlayout의 특성인 기기의 화면에따라 뷰의길이가 지정되는 특성이 활성화되지않았고 따라서 짤리는이슈가 발생했었다.
1.5.2
- 알람이 5분전 단일알람에서 5분전,0분전 두가지로 변경되었습니다.
—> 알람뷰모델에서 알람생성을 두개를 하도록 변경했고, 첫번째알람은 5분전 노티피케이션만발생하고, 두번째알람은 0분전 노티피케이션후 데이터베이스내용을 변경하여 알람을 뷰에서 제거했다.
- 화면상단의 등록된 몬스터에대한 타이머출력이 일-시-분-초 순으로 정렬됩니다.
—> alarm Recyclerview의 adapter 내부에서 정렬했던 Collections.sort()를 이부분에도 구성하여 window의 Overlay를통해 화면위에그릴때 정렬하여 service로 보내도록했다.
- 화면상단에 현재시간항상출력이 추가되었습니다. 개별적으로 on/off할수없습니다.
—> 기존의 overlay를하는 service클래스를 lifecycleService()로 변경하고, 코루틴을이용하여 Main스레드상에서 async(비동기)로 무한반복문을 돌려, 1초마다 system의 currentMiliseconds를 가져와서 뷰를 갱신하도록 했다.
- 도감부분의 UI가 개선되었습니다. 아이탬개수가 1개일때는 더이상표기되지 않습니다.
—> 기존의 도감아이템뷰들이 가독성이 떨어진다는 피드백을 받았고, recyclerview의 background색상을 하얀색으로, 아이템뷰를 cardview로 구성하지않고 구분선을 두고 layout_margin을통해 적절한거리를 벌려 좀더 보기좋게 구현했다.
- 알람부분의 버튼이름들이 더 직관적으로 변경되었습니다.
—> 기존의 버튼들의 이름이 너무 이해하기 어렵다고 피드백을 받아서, 버튼의이름을 좀더 직관적으로 무슨버튼인지 정확하게 표기해뒀다.
1.5.3
- 화면위에 타이머출력부분이 게임내UI와 겹쳐져 잘보이지 않던 이슈가 개선되었습니다.
—> 텍스트뷰를 상속받은 커스텀텍스트뷰 클래스를 생성하고, 커스텀뷰 클래스 생성시 요구되는 생성자3개 구현함. onDraw() 메소드를 오버라이드 하여 paint객체의 style을 STROKE로 설정해서 두께부분의 색깔을 지정하고 draw()하고, style을 FILL로 설정해서 글자색부분을 지정하여 draw()함
- on/off 버튼을통해 등록된타이머가 없어도 현재시간을 출력할수 있습니다.
—> on/off 버튼이 눌려졋을때, 기존에 타이머가 없을때 아무동작도 안하던것을 startService()하도록 변경하고, intent에 아무것도담지않아서 보냄 이후 service클래스 내에서 인텐트가 비어잇으면 시간만 출력하도록 변경함.
- 타이머가 모두 제거되어도, 현재시간은 계속 출력되도록 변경되었습니다.
—> 데이터베이스 내에서 타이머를 observer로 구독하여 변경사항이 감지되었을때, 기존에 타이머에등록된 것이 없을때 stopService() 하도록 한것을 startService()로 변경함. 이후 위와동일
- package 단위에 service,receiver,viewmodel 을 추가하여 기존에 item 패키지명에 있던 녀석들을 분가해서 더깔끔하게 정리했다.
1.5.4
- 화면위에그리기 Overlay 활성화 중에 업데이트 발생시 ANR 발생
—> onCreateView() 에서 overlay 권한 띄우기
댓글남기기