관련코드 Github주소 바로가기 - https://github.com/puze/AndroidCalendar.git

목표

  • MVC패턴의 이해
  • 뷰바인딩의 사용
  • 리사이클러 뷰의 사용
  • 어댑터 안에서의 바인딩 사용
  • 캘린더 클래스의 사용

MVC 패턴

MVC패턴이란 Model - View - Controller 로 이루어진 디자인 패턴이다.
이러한 패턴을 적용 시킴으로써 다른 사람이 코드를 파악하는 것이 용이하고
유지보수를 쉽게 할수 있게 한다.

  • 모델(Model)
    • 데이터의 집합이라고 볼 수 있다.
    • 데이터의 가공까지 포함한다.
    • 모델에선 뷰와 컨트롤러의 정보를 가지고 있지 않다. (독립적임)
  • 뷰(View)
    • 사용자에게 보여지는 부분을 담당한다.
    • 뷰에선 모델의 정보를 표시하지만 컨트롤러의 정보는 가지고 있지 않다. (모델에 종속적임)
  • 컨트롤러(Controller)
    • 사용자의 입력을 받는다.
    • 화면 뒤에서 모델과 뷰를 연결하는 역할을 한다. (모델과 뷰에 종속적)

독립적 : 어떤(혹은 해당) 클래스가 없어도 작동할 수 있다.
종속적 : 작동하기위해 해당 클래스가 필요하다.

이 디자인 패턴을 이 안드로이드 아키텍처에 적용시키면 어떻게 될까.
나름대로 MVC패턴으로 앞으로 만들 캘린더를 구조화를 시켜보자면

  • 모델 : 액티비티 구성에 필요한 항목의 데이터 -> 뷰 바인딩과 엑티비티에 독립적
  • 뷰 : 메인 액티비티의 뷰 바인딩(애초에 개발자가 구현하지 않음) -> 데이터와 엑티비티에 독립적
  • 컨트롤러 : 메인 액티비티 -> 데이터와 바인딩에 종속적

뷰(바인딩)가 모델에 독립적일 필요는 없지만 위의 대전제는 성립하므로 이런 구조를 생각해볼 수 있겠다.

사실 뷰가 모델에 독립적인 상태로 MVP패턴에 가깝다.
MVC와의 차이는 컨트롤러가 Presenter가 되고 뷰는 모델에 독립적이게된다.

액티비티는 뷰로서만 사용하고 액티비티 컨트롤러 클래스를 따로 작성하는 방법도 있다.

뷰 바인딩

  1. build.gradle 추가
    build.gradle에 다음을 추가한다.
android {
...
    buildFeatures {
        viewBinding = ture
    }
}
  1. 레이아웃 xml 작성
    엑티비티에 올라갈 레이아웃을 작성한다
  2. 뷰 바인딩을 초기화

위의 코드와 같이 lateinit 으로 레이아웃의 binding을 선언한 뒤
onCreate() 에서 binding에 inflate함수로 초기화를 한다.
액티비티의 setContentView는 binding.root를 파라미터로 불러옴으로써 레이아웃 역시 초기화가 가능하다.
이후 binding 변수를 통해 해당 액티비티의 id를 선언한 모든 뷰에 접근이 가능하다.

이전의 방식으로는 findOfViewId(R.id.AAA) 식으로 접근 하거나
잠깐 레이아웃에서 설정한 id에 직접 접근(Kotlin Synthetic)이 가능했었다.
출처 : https://todaycode.tistory.com/29

리사이클러뷰

  • 리스트뷰와 그리드뷰를 대신 할 수 있는 뷰
  • 어댑터를 이용해 뷰를 컨트롤
  • 이름으로부터 알수 있듯이 재활용이 포인트
  • 재활용 하는것은 각 항목(item)
    출처 :https://blog.hexabrain.net/363
  • 리사이클러뷰를 선언한 후 레이아웃 매니저를 통해서 초기화하여 사용한다.
  • 사용가능한 레이아웃은 그리드레이아웃, 리니어 레이아웃, 스태거드 그리드 레이아웃이 있다. "레이아웃 매니저 바로가기"

어댑터

위의 MVC패턴과 유사한 아이디어에서 나온 오브젝트이다.
컨트롤러부분을 담당하기위한 오브젝트이며 뷰에서 데이터의 가공과 로직을 배제하고 어댑터에서 작성한다.
구조는 다음과 같이 되겠다.

또한 리사이클러뷰 어댑터를 구현하는데 있어서 뷰홀더 또한 구현해야한다.
뷰홀더란 리사이클러 뷰에 들어갈 항목의 뷰 및 메타데이터를 뜻한다.

캘린더 구현

  1. 레이아웃 생성
    기본적으로 사용할 메인 액티비티와 리사이클러뷰를 포함한 레이아웃을 준비한다.
  2. 레이아웃 매니저 할당
    다음과 같이 리사이클러뷰의 레이아웃 매니저에 그리드 레이아웃 매니저를 할당한다.
  3. 모델(데이터) 생성
    캘린더 안에 들어갈 아이템의 데이터 클래스를 생성한다.
    우리는 항목에 들어갈 일(day)만 표시할 것 이므로 int형 day하나만 포함한다.
  4. 뷰홀더 구현
    어댑터를 구현하는데 있어서 필요한 뷰홀더도 구현하기로한다.
    현재 구현할 뷰 홀더는 캘린더 어댑터에 종속적이기 때문에
    캘린더 어댑터 클래스안에 inner class(nested class)로 작성한다.
    생성자 파라미터로는 항목에들어갈 레이아웃의 바인딩을 받아와
    init생성자에 해당 항목에 클릭리스너를 달아준다.
    또한 bind메소드를 구현하여 해당 항목이 어떻게 표시될지 작성을한다.
    파라미터로는 앞서 작성했던 모델을 사용하도록 한다.
  5. 어댑터 구현
    리사이클러 뷰의 어댑터를 상속받고 (RecyclerView.Adapter<CalendarAdapter.CalendarViewHolder>())
    오버라이딩이 필요한 메소드는 3개가 있다.
    • onCreateViewHolder : 뷰홀더를 생성할때 호출되며 여기서 생성한 뷰홀더 객체를 반환한다.
    • onBindViewHolder : 뷰홀더를 실제 데이터와 연결한다.
    • getItemCount : 관리해야할 아이템의 크기를 반환한다.
  6. 액티비티 구현
    리사이클러뷰의 구현에 필요한 준비는 끝났고 안에 담을 내용을 구현하자.
    캘린더를 구현하기 위해 Calendar클래스를 이용한다.
    이 클래스를 이용해 직접 구현하고 다루려면 힘든 달력관련 함수들을 손쉽게 다룰수 있다.
    캘린더 작성 알고리즘은 다음과 같다.
    • 캘린더를 현재 달의 1일로 설정한다.
    • 리사이클러 뷰에 들어갈 데이터리스트를 선언한다.
    • 1일이 시작하는 요일을 찾고 월요일부터 해당요일의 차를 빈 데이터로 넣는다.
    • 해당 월의 1일부터 해당 월의 마지막 일까지 넣는다.
    • 작업이 끝난 데이터리스트를 이용하여 어댑터를 생성하고 리사이클러 뷰의 어댑터에 할당한다.캘린더 클래스를 이용하여 현재달 역시 출력 할 수 있다.

확장 가능한 부분

  • 다른 날짜를 선택하여 해당 캘린더를 다시 그리는 기능
  • 액티비티에 종속되있지 않고 어디서나 불러올수 있게 모듈화 구현
  • 뷰홀더에 일 이외에 다른 데이터(메모 혹은 스케줄)도 표시

마치며..

캘린더를 작성하기위해 mvc패턴과 뷰바인더, 리사이클러뷰, 어댑터, 뷰홀더까지 여러 개념들을 소개하였다.
코드는 github(바로가기)에서 확인 가능하다.

협업하기 힘든 사람

  • 자신의 의견을 잘 전달하지 못하는 사람
  • 의견충돌이 있을 시 호전적으로 대화를 하는사람
  • 주위에 부정적인 감정을 유발시키는 사람
  • 일정관리를 못하는 사람
  • 오만한 사람

의견충돌의 원인

  • 설계
  • 코드리뷰
  • 미래 예측

의견충돌의 대처

  • 원칙과 철학에 관한 내용이라면 결론을 내려하지말자
  • 다양한 의견을 수렴하는 자세를 갖자
  • 철학과 정의가 타당하다고해서 무조건 옳지 않을 때도 있다. 현실의 한계점을 생각해보자
  • 겸손한 태도로 대처하자

글을 쓰게된 이유

Stateful Widget의 life cycle

[Flutter] Widget의 Life cycle

1. initState

플러터나 위젯의 라이프 사이클을 몰라도 익숙한 함수이다.
해당 위젯이 로드 될때 필요한 초기화를 진행하게 된다.
빌드 이전에 한번만 실행되며 안드로이드 스튜디오의 onCreate
유니티의 Awake 함수에 해당한다.

2. didChangeDependencies

initState직후에 호출된다.
또한 InheritedWidget으로부터 종속된 경우 그 위젯이 변경될때 다시 호출된다.

3. build

라이프 사이클을 통틀어 가장 중요한 부분으로 여기에서 위젯을 렌더링한다.
didChangeDependencies 이후 호출되며 다시 그리는 비용이 저렴하다.
UI를 렌더링 할때마다 호출된다.

4. didUpdateWidget

자주 접하게 될 라이프 사이클은 아니지만 상위 위젯이 변경되고
UI를 다시 그려야 할 때 호출된다.
파라미터로 oldWidget을 가져오고 현재 위젯과 비교하여 추가논리를 수행 할 수 있다.

5. dispose

initState와 반대의 라이프 사이클로 해당 개체가
위젯트리에서 영구적으로 제거되고 다시 빌드되지 않을 때 호출된다.
여기선 스트림을 구독 취소하거나 애니메이션을 삭제하는 등의 일을 하게된다.

build 이후에 한번만 실행

가령 처음에 위젯을 하나 생성후에 애니메이션을 재생시키고 싶다면
위의 라이프 사이클로는 수행 하기 어렵다.
하지만 방법이 있으니 바로 initState함수에 addPostFrameCallback함수를 호출 하는것이다.

1
2
3
4
5
  void initState() {
    super.initState();
    WidgetsBinding.instance
        .addPostFrameCallback((_) => yourFunction(context));
  }
cs

참고

라이프 사이클 : https://medium.com/flutter-community/flutter-lifecycle-for-android-and-ios-developers-8f532307e0c7

 

Flutter Lifecycle for Android and iOS Developers

One of the most confusing ideas transitioning from Android and/or iOS is to understand how Flutter handles its lifecycle.

medium.com

addPostFrameCallback : https://stackoverflow.com/questions/49466556/flutter-run-method-on-widget-build-complete

 

Flutter: Run method on Widget build complete

I would like to be able to run functions once a Widget has finished building/loading but I am unsure how. My current use case is to check if a user is authenticated and if not, redirect to a login...

stackoverflow.com

 

여기선 List에 위젯들을 추가 후 remove로 삭제할때 작동하지 않는 경우를 메모한다.

 

1. 현상

List<Widget>을 선언 한 후 add함수로 필요한 위젯들을 추가 하였다.

그리고 builder 팩토리 생성자로 리스트의 위젯들을 빌드하여 사용자에게 보여준다.

또한 동적으로 삭제도 할 수 있으며 setState함수에서 remove함수를 호출하였다.

하지만 리스트에서 아이템이 삭제가 되지않았다..

 

2. 해결

리스트의 remove함수가 아닌 removeWhere함수를 이용하라는 검색결과가 있었다.

사용방법은 C#에서 자주 사용한 Linq식과 똑같이 removeWhere((파라미터)=>판별식);

과 같이 사용하면 되고 이렇게 사용하는 편이 요소를 지정하는데 애매하지 않고 확실하다고 생각한다.

나는 각 요소에 key값을 주어 이 key값을 비교해 삭제하는 판별식을 작성하였다.

 

참고 : https://stackoverflow.com/questions/66314111/how-to-remove-an-element-from-list-of-widgets-in-flutter/66315505

 

How to remove an element from List of Widgets in flutter?

I made a list of TableRows in flutter whichI passed as a children to my Table widget .Each row has some data and a flat button at the end which deletes the data from by database and I want to delet...

stackoverflow.com

 

1. 객체지향언어에서의 상속

객체지향언어에서는 코드의 재사용성을 위해 상속을 사용하기도 한다.

실제로 라이브러리의 유용한 클래스를 상속받아 프로젝트에 맞게 커스터마이징을 한 후 사용하기도 한다.

 

2. 플러터의 구조

플러터는 기본적으로 위젯이라는 객체들로 구성되어있으며 하나의 위젯트리를 형성한다.

앱의 기본이 되는 MaterialApp과 Scaffold역시 위젯이며 이 위젯을 시작으로 수 많은 자식 위젯들로 하나의 앱이 완성된다.

플러터는 수 많은 위젯들을 준비하여 두었고 우리는 그 위젯들을 적재적소에 구성하기만 하면된다.

가령

가운데 정렬을 하고싶다 : Center 위젯

버튼을 삽입하고싶다 : ElevatedButton 위젯

이미지를 삽입하고싶다 : Image 위젯

가운데 정렬된 버튼 안에 이미지를 삽입하고싶다 : Center - ElevatedButton - Image

처럼말이다.

 

3. 위젯을 상속

프로젝트를 진행하다보면 프레임워크나 라이브러리에서 제공하는 위젯만 가지고는 필요한 기능이 부족할 수 도 있다.

그럴땐 보통 위의 설명과 같이 클래스를 상속받아 기능을 확장하는것이 일반적인데..

실제로 위젯을 상속받아 구현을 해보려고 했지만 생성자의 구성과 Null safty 로 인해 아무 기능도 확장도 하지않은 클래스가 이미 복잡해져버려서 정상적인 위젯의 상속방법을 찾아 검색에 나섰다.

기본적인 Dart 문법으로 상속을 하는 예제는 많았지만 이미 정의된 위젯을 상속받는 예제는 찾아 볼 수 없었고 그 대신 아래와 같은 글을 찾을 수 있었다.

https://stackoverflow.com/questions/51476234/subclass-a-class-that-extends-statelesswidget-or-statefulwidget-class

 

Subclass a class that extends StatelessWidget or StatefulWidget class

Is it possible to create a class that extends a class extending StatelessWidget or StatefulWidget. For example: class MyButton extends StatelessWidget { final String label; Button({this.label}); @

stackoverflow.com

https://flutter.dev/docs/resources/faq#can-i-extend-and-customize-the-bundled-widgets

 

FAQ

Frequently asked questions and answers about Flutter.

flutter.dev

 

Can I extend and customize the bundled widgets?

Absolutely. Flutter’s widget system was designed to be easily customizable.

Rather than having each widget provide a large number of parameters, Flutter embraces composition. Widgets are built out of smaller widgets that you can reuse and combine in novel ways to make custom widgets. For example, rather than subclassing a generic button widget, ElevatedButton combines a Material widget with a GestureDetector widget. The Material widget provides the visual design and the GestureDetector widget provides the interaction design.

To create a button with a custom visual design, you can combine widgets that implement your visual design with a GestureDetector, which provides the interaction design. For example, CupertinoButton follows this approach and combines a GestureDetector with several other widgets that implement its visual design.

Composition gives you maximum control over the visual and interaction design of your widgets while also allowing a large amount of code reuse. In the framework, we’ve decomposed complex widgets to pieces that separately implement the visual, interaction, and motion design. You can remix these widgets however you like to make your own custom widgets that have full range of expression.

 

4. 결론

위 글들의 결론은 플러터에서 이미 만들어진 위젯(StatelessWidget 또는 StatefulWidget위젯을 이미 상속받은 위젯)의 기능을 추가하기 위해 위젯을 상속 받는것이 불가능한것은 아니지만 상속이 아닌 구성을 추천한다는 것이다. 

이 방법이 익숙치는 않지만 mixin키워드와 같은 키워드와 더불어 원하는 위젯을 만드는데 어려움은 없어보인다

1. 소개

Dart가 Java나 C#같은 언어와 다르다고 느꼇던 부분이 다중상속을 지원하지 않는 것이다.

 

인터페이스는 지원하지만 각 클래스마다 내용을 구현해야하는 번거로움이 있을 것이다.

 

다음과 같이 부모클래스 이외에 다른 클래스의 메소드를 재사용 하고싶을때에는 어떻게 할까?

 

그림1. 상속관계 : 이걸 어떻게 구현할까?

 

그럴때 이 mixin 기능을 이용한다.

 

2. 언어적 표현

성기사를 한번 디자인 해보자.

Person 클래스를 구현하고, Person 클래스를 상속받은 Knight 클래스를 구현한다.

그리고 마지막으로 Knight를 상속받아 Holy knight를 구현한다.

기본적으로 OOP에서는 기본적으로 지원하는 선형적인 상속관계가 완성되었다.

그러나 재사용 가능성이 있는 Holy skills는 어떻게 구현하면 될까.

 

mixin대상이 될 (Holly skills) 클래스를 구현하고

이 클래스를 사용 할 (Holly Knight)클래스가 with 키워드로 mixin하면된다.

 

여기서 제한 사항이 하나 있는데 mixin의 대상이 될 클래스는 생성자를 선언하면 안된다는 것이다.

또한, 일반적인 방법으로 클래스를 사용하고 싶지않고 (인스턴스화 하거나 상속을 받게함)

mixin클래스로만 활용하고 싶다면 class 키워드 대신 mixin 키워드로 선언하자.

 

3. 구현

위의 내용을 구현한 코드는 아래와 같다.

 

abstract class Person{}

abstract class Knight extends Person{}

mixin HolySkills {
  void holySkill(){
  print('Holy Skill');
  }
}

class HolyKnight extends Knight with HolySkills{}

void main() {
  HolyKnight holyKnight = HolyKnight();
  holyKnight.holySkill();
}

1. 서론

액션바를 구현하기위해 공식문서 https://developer.android.com/training/appbar/setting-up 와

 

앱 바 설정하기  |  Android 개발자  |  Android Developers

가장 기본적인 형태의 작업 모음은 한쪽에는 활동 제목을 표시하고 다른 쪽에는 더보기 메뉴를 표시합니다. 앱 바는 이렇게 간단한 형태로도 유용한 정보를 사용자에게 제공하고 일관된 디자인

developer.android.com

https://recipes4dev.tistory.com/149 블로그를 참고해 만들어 보기 시작했다.

 

안드로이드 툴바 기본 사용법. (Android Toolbar)

1. 안드로이드 툴바(Toolbar)와 앱바(App Bar) 툴바(Toolbar)는 안드로이드 5.0 (API Level 21)부터 추가된 위젯(Widget)으로, 앱에서 가장 중요한 액션 또는 가장 자주 사용되는 액션들을 제공하는 앱바(AppBar)..

recipes4dev.tistory.com

위에서 소개된 도구(android.support.v7.widget.Toolbar)를 사용하기위해 의존성에

implementation 'com.android.support:appcompat-v7:27.1.1'를 추가해야하는데 실제로 추가해보면

Version 28 (intended for Android Pie and below) is the last version of the legacy support library, so we recommend that you migrate to AndroidX libraries when using Android Q and moving forward. The IDE can help with this: Refactor > Migrate to AndroidX...

라는 오류메시지를 뿜게된다. 말머리에 굳이 안드로이드 스튜디오 버전을 적은 것은 버전업에 따라 마이그레이션이 필요해지고 정보를 찾아 해매게 되어 적게 된 것이다.

마이그레이트가 필요하다니 안드로이드 스튜디오에서 원하는대로 해보자.

 

2. 구현

커스텀 액션바를 만들기 위해 위에서 언급한 액션바를 추가할 xml 파일에 <android.support.v7.widget.Toolbar> 대신 <com.google.android.material.appbar.MaterialToolbar>를 사용하도록 한다. 그리고 내 마음대로 커스텀을 하면된다.

예를 들어 가운데 정렬을 하기위해선 다음과같이 app:titlecentered="true"를 지정한다.

1
2
3
4
5
6
7
8
9
10
11
    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/app_actionbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="?attr/actionBarTheme"
        app:title="Action Bar"
        app:titleCentered="true"
        app:titleTextColor="@color/black"
        android:elevation="4dp">
    </com.google.android.material.appbar.MaterialToolbar>
cs

위와같은 방법으로 정렬을 하지 않고 툴바 태그안에 레이아웃을 넣고 정렬을 하면 네비게이션 아이콘이나 메뉴 아이콘에 영향을 받게된다.

 

시험삼아 메뉴 아이콘을 추가해보자.

액션바에 추가할 메뉴 xml을 작성한다. (res/menu/)

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/test_action1"
        android:title="test1"
        app:showAsAction="never"/>
    <item
        android:id="@+id/test_action2"
        android:title="test2"
        app:showAsAction="never" />
</menu>
cs

액션바를 추가할 Activity에 (AppCompatActivity를 상속받은 Activity) 다음과 같은 onCreateOptionsMenu를 오버라이딩 해준 후 위에서 준비한 메뉴 xml 파일을 inflate 해준다.

1
2
3
4
5
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.actionbar_actions, menu);
        return true;
    }
cs

 

 

완성된 커스텀 액션바

 

 

참고:https://github.com/material-components/material-components-android/issues/2011

 

[Toolbar] How to center title of MaterialToolbar? · Issue #2011 · material-components/material-components-android

I am trying to center the title of the MaterialToolbar, tried to add android: gravity but it doesn't work. There is no info in the documentation on how to center it <androidx.coordinatorlayo...< p=""> </androidx.coordinatorlayo...<>

github.com

 

1
2
int pxValue = 30;
int dpValue = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, pxValue , mainContext.getResources().getDisplayMetrics());
cs

+ Recent posts