들어가며

코틀린은 이미 정의된 클래스를 확장 시킬 수 있는 기능이 있다. 예를 들어서 아래와 같이 String 클래스에 원하는 메서드를 추가 할 수 있다.

fun String.getFirst() = this[0]
println("hello".getFirst())
//print 'h'

원리

원리는 생각보다 간단하다. 자바에서 static을 사용하여 기존 클래스를 입력받아 처리하는 메서드를 생성함으로써 사용자 입장에서는 기존의 클래스를 확장시킨 것처럼 보이게 하는 것이다.

public static String getFisrt(String string){ return string[0];}

Question?

그렇다면 만약 상속 관계에 있는 두 개의 클래스를 이용하여 아래와 같이 코드를 작성하면 어떤 문자열이 출력될까?

open class Parent 
class Child: Parent()

fun Parent.foo() = "parent" 
fun Child.foo() = "child"

fun main(args: Array<String>) { 
  val parent: Parent = Child()
  println(parent.foo()) 
// 여기서 출력되는건 무엇일까?
}

Answer & Why

원리를 알고있다면 추론하는 것은 간단하다. 위 코드가 자바로 변환 되었다고 생각해보자. static을 이용하므로 아래와 같이 변환 되었을 것이다.

//... 클래스 정의는 생략

public static String foo(Parent parent){return "parent";)
public static String foo(Child child){return "child";)

public static void main(String[] args){
	Parent parent=new Child();
	System.out.println(foo(parent));
}

자바 공부를 열심히 했다면 결과는 "parent"가 출력되었을 것이라고 바로 알았을 것이다. 자바는 오버로딩을 통해서 어규먼트와 파라미터가 일치하는 함수를 호출하기 때문이다. 위 코드를 보면 오버라이딩을 통해서 "child"가 출력될수 있다고 착각할 수있다. 그러나 확장함수는 인스턴스의 실제 값과는 관련없이 클래스에만 선언되기 때문에 함수를 호출할 때 인스턴스의 클래스 종류만을 따른다.

Question2 : Member vs Extension

자 그럼 만약 확장함수를 기존 클래스의 멤버 메소드를 오버라이딩 하는 것처럼 사용하면 어떻게 될까?

class A{
	fun foo()="member"
}
fun A.foo()="extension"

println(A().foo()) //??

Answer & Why

정답은 "member"를 출력한다. 왜냐하면 코틀린은 확장함수가 멤버 함수를 오버라이딩 하는 것을 허용하지 않는다. 왜 그럴까? 만약 반대로 허용한다고 생각해보자, 코드를 작성 할때 전역에서 확장함수로 Array.get()을 새로 정의 했다고 하자. 추후 다른 사람이 이 파일에 새로운 코드를 추가한다고 했을 때, get()을 본래의 의도로 사용하여 작성한다면 코드는 버그 투성이가 될 것이고 이를 인지하고 본래의 함수를 사용하고자 해도 확장함수로 정의된 이상 불가능하다.

하지만 코틀린은 확장함수로 오버로딩을 하는 것은 허용한다. 이는 기존의 멤버 메소드에 영향을 끼치지 않기 때문이다.

class A{
	fun foo()="member"
}
fun A.foo(i:Int)="extension $i"

println(A().foo(2)) //extension 2

 

Basics

Extension Function은 클래스를 확장한다. 클래스 밖에서 정의 되지만 regular 멤버로 클래스 내부에서 호출할 수 있다.

fun String.getLast(number: Int) = this.get(this.length-1)
class Test{
 fun main(){
        val c: Char ="abc".getLast()
    }
}

Receiver

확장 함수가 호출 될 때는 this가 reveiver로서 호출된다. 또한 기본적으로 this의 멤버 변수나 함수를 호출하기 위해서 일일이 this를 정의할 필요가 없다.

fun String.getLast(number : Int)=get(length()-1)

Import

extension을 이용하기 위해선 import를 이용해서 명시해주어야 한다. 왜냐하면 extension function은 기본적으로 전체프로젝트에서 사용할 수 없도록 되어있기 때문이다.

fun String.lastChar()=get(length-1)

import com.example.util.lastChar
val c: Char="abc".lastChar()

Calling Extension Functions from Java code

자바에서 코틀린의 확장함수를 호출할때는 static형태로 호출된다. 이 또한 import를 통해서 static한 함수만을 호출 할 수 있다.

//StringExtension.kt
fun String.lastChar()=get(length-1)

//JavaClass.java
import static StringExtensionKt.lastChar;
char c=lastChar("abc");
//or
char c=StringExtensionKt.lastChar("abc");

위에서 보듯 자바에서 확장함수를 호출할때는 첫 번째 파라미터로 리시버를 입력한다. 확장함수가 추가적인 파라미터가 필요하다면 자바에서는 두 번째 파라미터부터 입력하면 된다.

//StringExtension.kt
fun String.getChar(pos : Int)=get(pos)

//JavaClass.java
import static StringExtensionKt.getChar;
char c=getChar("abc",2);

Accessing private members

자바에서는 다른 클래스의 static 함수에서 private member를 호출할 수 없다.

코틀린의 확장함수는 분리된 보조 클래스에서 정의된 static 함수이다.

따라서 확장함수에서는 private member를 호출 할 수 없다.

Reference

Examples from the Standard Library - Starting up with Kotlin | Coursera

들어가며

파이썬, 자바등의 많은 프로젝트에서는 이식성을 위한 코드의 집합인 패키지, 모듈이 있다. 당연히 안드로이드에도 이를 지원하는 AAR(Android Archive)가 존재한다. 공식 사이트에도 설명이 되어있지만 갱신되지 않거나 모호한 부분이 있어 차근차근 모듈을 만들고 테스트하는 방법에 대해서 알아보도록 하자.

안드로이드의 모듈은 코드뿐만 아니라 이미지, 텍스트 등의 데이터를 포함해서 부르기 때문에 여기선 라이브러리와 동일한 의미로 언급한다.

 

1. 새로운 모듈 만들기

우선 기존의 프로젝트에서 아래의 경로를 통해 새로운 모듈을 만든다.

New Module을 클릭하면 여러가지 타입이 나오는데 간단한 AAR을 만들기 위해서 Android Library를 선택하고 next를 클릭한다.

다음에는 모듈이름과 패키지명, Minimum SDK를 설정할 수 있다. 패키지명은 지금은 중요하지 않지만 추후에 배포를 할 시에는 중요하다. 지금은 기본설정으로 진행해보자.

새로 생성을 하면 Project 패널에 모듈명으로 폴더가 생성되어 있으며 내부에는 기존 app과 같은 구조로 표현되어있다.

ToastID와 관련된  build.gradle 파일을 살펴보면 기존의 app build.gradle과 매우 유사하다, 하지만 plugins에 'com.android.application'대신 'com.android.library'라고 되어 있는데 이는 ToastID디렉토리를 라이브러리로 사용하겠다는 뜻이다. 그리고 라이브러리에는 applicationID가 없지만 app에는 있다. 이는 라이브러리에서는 applicationID를 사용할 수 없기 때문이다.

Android 공식 사이트에는 새로운 모듈을 만들었을 때 gradle.build설정을 따로 해야한다고 하지만 현재는 자동으로 라이브러리를위한 gradle.build를 만들기 때문에 신경쓰지 않아도 된다. (패치했으면 document는 업데이트 해줬으면 좋겠다...)

https://developer.android.com/studio/projects/android-library
 

Android 라이브러리 만들기  |  Android 개발자  |  Android Developers

Android 라이브러리를 생성하는 방법을 알아보세요.

developer.android.com

build.gradle(ToastID)
build.gradle(app)

간단한 테스트를 하기 위해서 ToastID클래스와 아이디를 토스트하는 메서드를  작성해보았다.

자 그럼 모듈을 기존의 app에 연결하여 어플리케이션을 빌드해보자

 

2. 로컬 모듈 연결하기

build.gradle(app)에 들어가서 모듈과의 연결을 설정할 수 있다. 아는 사람도 많겠지만 dependencies에서 외부 저장소 라이브러리를 가져오거나 지금 할 것처럼 프로젝트 내부의 모듈을 연결 할 수 있다. 프로젝트 내부 연결을 위해선 implementation project(':모듈명')를 dependencies에 추가하면된다.

추가후 Sync Now를 클릭해서 동기화 해주면 기존의 app 내부의 MainActivity에서 ToastID모듈 내부의 toast 메서드를 참조할 수 있는것을 볼 수 있다. 이제 모듈을 마음껏 테스트해보고 필요시 배포를 할 수 있다.

 

마치며

자바에 대해서 어느정도 지식이 있는 사람은 "그냥 JAR을 디렉토리에 옮겨서 import하면 쉬운데 build.gradle을 어렵게 설정하면서까지 해야하는거지?"라고 생각할 수도 있다. 물론 이 방법도 가능하다. 그러나 눈치챈 사람도 있겠지만 AAR은 코드 뿐만 아니라 drawable,layout 등의 안드로이드에 특화된 데이터까지 모듈화가 가능한게 큰 장점이다. 이를 잘 사용해서 프로젝트 협업을 잘 하거나 안드로이드 개발자들을 위한 오픈소스를 쉽게 사용하도록 제공해보도록 하자.

들어가며

안드로이드의 가장 기본적인 알림을 사용해보자. 아마 프로젝트 하다가 어떻게 쓰는지 궁금한 사람들이 검색을 했을 테니 최대한 간결하게 코드를 통해 설명하겠다. 코드를 세세하게 찾아보고 싶은 사람은 android developers의 문서를 참조하기 바란다.

 

사용방법

1. Notification Channel만들기

각 어플리케이션에서 Notification알림을 실행하려면 우선 채널을 만들어야한다. 

아래 코드를 복사해서 MainActivity에 추가하고 onCreate에서 해당 함수를 호출해주자

*채널명과 설명은 본인 어플리케이션에 맞게 설정해주자

private void createNotificationChannel() {
        // Create the NotificationChannel, but only on API 26+ because
        // the NotificationChannel class is new and not in the support library
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = "공지사항 채널";
            String description = "공지사항 채널";
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel("공지사항 채널", name, importance);
            channel.setDescription(description);
            // Register the channel with the system; you can't change the importance
            // or other notification behaviors after this
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }

2. 새로운 Notification을 만들고 이를 NotificationManager에 추가해주자

URLData.activity는 호출을 위한 액티비티 Context를 추가해주면된다.(ex MainActivity.this)

플래그를 설정해주고 PendingIntent를 호출하자.

builder를 만들 때 setContentIntent에서 이를 추가하고 Notification을 위한 다른 정보를 추가해주자(제목, 내용 등)

마지막으로 액티비티에서 NotificationManagerCompat를 호출해주고 notify로 builder로 만든 Notification을 추가해주면 끝이다.

Intent intent = new Intent(URLData.activity, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(URLData.activity, 0, intent, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(URLData.activity, "공지사항 채널")
                                    .setSmallIcon(R.drawable.app_icon)
                                    .setContentTitle(urlDataList.get(index).urlName)
                                    .setContentText("새로운 공지사항이 등록되었습니다!!")
                                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                                    .setContentIntent(pendingIntent)
                                    .setAutoCancel(true);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(URLData.activity);
notificationManager.notify('1', builder.build());

+ Recent posts