코틀린 기반으로 쓰여짐


 

 

이메일을 통한 회원가입시에 특정 도메인에서만 이용가능하게 하고싶어서(가령 블라인드나 에브리타임같은) 이메일 인증이 필요했다.

파이어베이스를 통하면 쉽게 구현이 가능하긴하다.

https://firebase.google.com/docs/auth/android/manage-users?hl=ko 

 

Firebase에서 사용자 관리하기  |  Firebase 문서

Google I/O 2022에서 Firebase의 새로운 기능을 확인하세요. 자세히 알아보기 의견 보내기 Firebase에서 사용자 관리하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세

firebase.google.com

근데 파이어베이스를 쓰지 않고 싶기도했고, 직접 구현해보고 싶어서 만들어보았다.

최초에 만들어보려고 구글링을 해보니

https://stickode.tistory.com/287

 

 

[Kotlin][Android] 이메일 보내기 기능 만들기

안녕하세요! 이번 시간에는 인텐트를 활용해 이메일을 보내는 기능을 만들어보겠습니다. 구현은 안드로이드 버전 28에서 진행했습니다. 먼저 레이아웃을 만들어줍니다. activity_send_mail.xml <?xml ver

stickode.tistory.com

 

요런 방식이 대부분이였다. 잘 작동했지만, 내가 원하는 기능은 아니였다. 

어떤 앱/웹에서 인증번호 보내기 버튼을 누르게되면 메일 선택 없이 자동으로 보내지기 때문이다

그러한 방식이 무엇일까 구글링 해보다 찾은게 smtp 방식이다.

https://www.cloudflare.com/ko-kr/learning/email-security/what-is-smtp/

아마 대부분의 전공자들이 한번쯤은 들어봤거나 공부해본 방식일테다

simple mail transfer protocool의 준말로 말그대로 간편하게 이메일을 보낼때만 적용할 수 있는 프로토콜로서

tcp 25번 포트를 쓴다.  뭐 그게 중요한건 아니니까 더 궁금한게 있으신분들은 구글링 해보길 바란다.

 

1.smtp방식을 쓰려면 라이브러리를 추가해야한다

https://code.google.com/archive/p/javamail-android/downloads

 

Google Code Archive - Long-term storage for Google Code Project Hosting.

 

code.google.com

app -> libs 밑에다가 위 파일들을 설치하면 되는데 libs가 없으면 그냥 만들면된다

https://stackoverflow.com/questions/1334802/how-can-i-use-external-jars-in-an-android-project/6859020#6859020

 

How can I use external JARs in an Android project?

I have created an Android project and added an external JAR (hessian-4.0.1.jar) to my project. I then added the JAR to the build path and checked it off in Order and Export. Order and Export is ig...

stackoverflow.com

 

 

모르겠는 분들을 위한 참고자료

 

2.메니페스트 변경

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

추가해준다

 

3.의존성 주입

implementation files('libs/activation.jar')
implementation files('libs/additionnal.jar')
implementation files('libs/mail.jar')

 

4.보안비밀번호 만들기

https://pdi-mz-support.zendesk.com/hc/ko/articles/360043927071--Account-2%EB%8B%A8%EA%B3%84-%EC%9D%B8%EC%A6%9D-%ED%99%9C%EC%84%B1%ED%99%94-%EA%B0%80%EC%9D%B4%EB%93%9C

 

[Account] 2단계 인증 활성화 가이드

1. 2단계 인증을 사용할 계정으로 로그인 후, Google 계정 페이지 > 보안 설정으로 이동 후, 2단계 인증란을 클릭합니다.  https://myaccount.google.com/security   2. 시작하기 버튼 클릭 3. 휴대폰 번호 입력

pdi-mz-support.zendesk.com

 

 

https://pdi-mz-support.zendesk.com/hc/ko/articles/360050293051--Gmail-2%EB%8B%A8%EA%B3%84-%EC%9D%B8%EC%A6%9D-%EC%82%AC%EC%9A%A9-%EC%8B%9C-%EC%95%B1-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8-%EC%83%9D%EC%84%B1%ED%95%98%EC%97%AC-%ED%83%80-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%B3%B4%EC%95%88%EC%88%98%EC%A4%80%EC%9D%B4-%EB%82%AE%EC%9D%80-%EC%95%B1-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0

 

[Gmail] 2단계 인증 사용 시 앱 비밀번호 생성하여 타 클라이언트(보안수준이 낮은 앱) 연결하기

앱 비밀번호란? 앱이나 기기에 내 Google 계정 액세스 권한을 부여하는 16자리 비밀번호입니다. 2단계 인증을 사용 중이며 Google 계정에 액세스하려 할 때 '비밀번호가 잘못됨' 오류가 표시되는 경

pdi-mz-support.zendesk.com

 

기본적으로 구글(지메일)을 이용해서 보내기 때문에 필요하다.

 fun sendEmail(toEmail: String): String {
 
 	val fromEmail = "아까위에서2차인증받았던이메일"
    val password = "아까위에서2차인증받았던 비밀번호"
    val code = (100..10000).random().toString()

        CoroutineScope(Dispatchers.IO).launch {
            val props = Properties()




            props.setProperty("mail.transport.protocol", "smtp")
            props.setProperty("mail.host", "smtp.gmail.com")
            props.put("mail.smtp.auth", "true")
            props.put("mail.smtp.port", "465")
            props.put("mail.smtp.socketFactory.port", "465")
            props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory")
            props.put("mail.smtp.socketFactory.fallback", "false")
            props.put("mail.smtp.ssl.enable", "true")
            props.setProperty("mail.smtp.quitwait", "false")
            // 구글에서 지원하는 smtp 정보를 받아와 MimeMessage 객체에 전달
            val session = Session.getDefaultInstance(props, this@GMailSender)


            // 메시지 객체 만들기
            val message = MimeMessage(session)
            message.sender = InternetAddress(fromEmail)                                 // 보내는 사람 설정
            message.addRecipient(Message.RecipientType.TO, InternetAddress(toEmail))    // 받는 사람 설정
            message.subject =
                "verfication code"                                              // 이메일 제목
            message.setText("저쪽 테이블에서 보낸 코드입니다. 아래 비밀번호를 인증창에 입력해주세요\n" + "<" + code + ">")                                               // 이메일 내용

            // 전송
            Transport.send(message)


        }
        return code
    }

 

5.결과


참고

https://min-wachya.tistory.com/168

 

[안드로이드] 메일 보내기

실행 결과 to : ~@naver.com from : ~@gmail.com 1, 라이브러리 추가 https://code.google.com/archive/p/javamail-android/downloads Google Code Archive - Long-term storage for Google Code Project Hosting..

min-wachya.tistory.com

https://heegyukim.medium.com/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%EC%97%90%EC%84%9C-smtp-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EB%B3%B4%EB%82%B4%EA%B8%B0-kotlin-b5111eee92f6

 

안드로이드에서 SMTP 이메일 보내기 (Kotlin)

안드로이드에서 Intent로 메일 앱을 띄우는 게 아니라, 직접 SMTP서버로 요청을 보내서 Gmail로 메일을 보내도록 구현을 해야 하는 일이 생겼다. 일단 Javax의 기본 Mail 라이브러리를 이용하면 간단하

heegyukim.medium.com

 

코틀린 기반으로 쓰여짐


 

기사 시험을 공부하다보면 싱글톤 패턴이라는 것을 만나게 된다.

사실 처음 기사시험을 공부할때나 학과 공부를 할때 이게 그래서 왜 필요한데에 관한 의문을 정말 많이 가졌다.

안드로이드를 공부하다보니, 자원의 효율적인 사용을 고민하게 되었고 그 결과 싱글톤 패턴이 왜 필요한지 어느정도 감을 갖게 되었다.

 

 

1)싱글톤 패턴이란?

  • 어떤 클래스의 인스턴스가 하나임을 보장하는 전역에서 접근 가능한 디자인 패턴
  • 처음부터 끝까지 한 번의 인스턴스를 생성함으로서 메모리의 효율적인 사용을 가능하게 함

 

2)왜 필요한데?

 

사실 왜 필요한지가 잘 납득이 안되어서 처음에는 이해가 잘 안되었다. 클래스의 인스턴스가 하나임을 보장하면 뭘 할 수 있는가에 관한 의문이 들었기 때문이다. 쉽게 얘기하면 데이터 베이스를 수정할 수 있는 인스턴스가 여러가지라면 어떨까? 당연히 문제가 생길것이다. 그러한 연유로 싱글톤 패턴을 사용한다.

 

3)어떻게 사용해?

 

이를 고민하기 전에 좀 더 익숙한 자바 코드로 한번 보자.

 

public class DBHelper {
	private static DBHelper instance;
    
    private DBHelper() {}
    
    public static DBHelper getInstance() { 
    	if(instance ==null) {
        	instance = new DBHelper();
        }
        return instance;
    
    }
    
 }

 

외부에서 인스턴스 못만들게 private으로 만들어 놓고

인스턴스가 있으면 그냥 리턴해주고 없으면 만들어서 리턴해준다. 즉 인스턴스가 무조건 하나 밖에 만들어 질 수 없는 구조이다

이를 코틀린에서는 object라는 키워드로 간단하게 지원해 준다. 

https://developer.android.com/reference/java/lang/Object

 

Object  |  Android Developers

android.net.wifi.hotspot2.omadm

developer.android.com

 

object DBHandler {}
val dbHandler = DBHandler

 

참 간단하다. 이를 통한 싱글톤 패턴의 구현은 문제가 있다.

 

1)파라미터가 없는 인스턴스만 생성이 가능하다.

 

2)메모리 낭비가 심하다.

 

1번은 뭐 이해되는데 2번은 무슨말이냐?

자바 코드의경우 getInstance()가 실행될때 인스턴스가 만들어진다. 그에 비해 아래 코틀린은 프로세스가 시작 될 때 만들어진다.

즉 인스턴스를 쓰지 않고 있을때도 만들어진다는 것이다. 

우리는 두가지 문제를 해결해볼 것이다

 

1)

class DBHandler private constructor(context:Context) {
	companion object {
    	private var instance : DBHanlder? = null
        
        fun getInstance(context:Context) =
        	instace ?: DBHandler(context).also {
            	instance = it
            }
    }

}

 

1번 문제는 간단하게 해결 할 수 있다.

그냥 구현하면 된다. 자바의 코드와 동일한 효과를 낸다.

근데 여기서 우리는 하나의 모르는 키워드를 하나 더 만나게 된다.

companion object 가 뭐지?

 

1-1) companion object vs object

 

앞서 얘기했듯이 object 키워드는 싱글톤 패턴을 만들어 준다.

근데 코틀린에서는 static 키워드를 지원하지 않는다.  그래서 클래스 인스턴스 없이 함수의 내부에 접근하고 싶을때 companion object를 쓴다

 

2)

이 역시 lazy 키워드를 통해 간단하게 해결 가능하다.

class DBHelper private constructor() {
	companion object {
    	val instance:DBHelper by lazy {~~~~~}
    }

}

 

4)또 다른 문제가 생겼어

 

위에서 구현한 싱글톤 패턴의 또 다른 문제가 있다. 쓰레드가 하나일 때만 쓰레드 세이프를 보장한다는 거다.

즉 쓰레드가 동시에 인스턴스를 생성하는 경우에는 여러개의 인스턴스가 만들어 질 수 있다는 것이다.

 

 

class DBHandler private constructor (context:Context) {
	companion object {
    
    @Volatile
    private var instance:DBHandler?= null
    
    fun getInstance(context:Context) =
    instacne?:synchronized(DBHelper::class.java) {
    	instance ?: DBHandler(context).also {
        	instance = it
        }
    
    }
    
    }


}

 

위와 같이 구현이 가능하다. null check를 두번 함으로써 thread-safe를 보장한다.

4-1)근데 Volatile 이 뭔데

휘발성이 있는 뭐 이런뜻인거 다 알긴아는데 저게 무슨 키워든데?

이는 구조를 좀 알아야한다

일반적으로 쓰레드는 메인메모리에 접근할때 내부 성능향상을 위해 cpu캐시에 저장하게 된다.

근데 쓰레드가 두개 이상이라면 쓰레드 간의 접근 시간의 차이로 이미 인스턴스가 만들어졌음에도 cpu에 이를 전달하지 못하여

다른 쓰레드가 또 인스턴스를 만들게 된다. 이를 방지하기 위한 키워드로, 메인 메모리로부터 읽어온 값을 캐시에 저장하지 않고 

메인메모리에 바로 저장한다.

 

근데 이 방법이 찾아보니까 여러가지 말이 많은거 같다

https://herdin.github.io/2020/12/25/about-double-check-locking

 

Epu Baal – developer from pamukkale

developer from pamukkale

herdin.github.io

 

대부분의 경우 holder를 통해 이를 보강하긴 하는데 이는 말이 많은거 같다. 본인이 선택해야 할 문제이다.

 

 


참고

https://jaejong.tistory.com/105

 

[Kotlin] 코틀린 기본 - object / Companion Object(동반 객체)

Kotlin - object 키워드 + Companion Object (동반 객체) Kotlin object 키워드 object는 흔히 JAVA에서 사용하는 무명 내부 클래스(anonymous inner class) 처럼 사용할 수 있습니다 object 키워드는 클래스를..

jaejong.tistory.com

https://herdin.github.io/2020/12/25/about-double-check-locking

 

Epu Baal – developer from pamukkale

developer from pamukkale

herdin.github.io

https://www.charlezz.com/?p=45959 

 

코틀린/자바의 volatile에 대해서 | 찰스의 안드로이드

volatile이란? 자바의 volatile 키워드 또는 코틀린의 @Volatile 애노테이션을 변수 선언시 지정할 수 있다. 사전적 의미로는 '휘발성의'라는 뜻을 가지며, 변수 선언시 volatile을 지정하면 값을 메인 메

www.charlezz.com

https://kotlinworld.com/166

 

[Kotlin] object 를 이용한 싱글톤 패턴 구현

개발을 하다보면 객체에 대한 하나의 인스턴스만 필요할 때, 하나의 인스턴스를 재사용하기 위해 싱글톤 패턴을 구현해야 할 일이 생긴다. *싱글톤 패턴 : 객체의 인스턴스를 1개만 생성하여 계

kotlinworld.com

https://www.inflearn.com/course/알기쉬운-modern-android

 

냉동코더의 알기 쉬운 Modern Android Development 입문 - 인프런 | 강의

이 강의의 목적은 Android Jetpack을 중심으로 한 안드로이드 라이브러리의 동작 원리를 이해하고 앱에 적용하는 법을 알기 쉽게 전달하는 것입니다., - 강의 소개 | 인프런...

www.inflearn.com

 

코틀린 기반으로 쓰여짐


 

구글에서는 안드로이드 제트팩의 일부분인 프레그먼트를 지원하며 이를 권장한다.

https://developer.android.com/guide/components/fragments?hl=ko 

 

프래그먼트  |  Android 개발자  |  Android Developers

A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section

developer.android.com

쉽게 얘기하면 밑에 네비게이션 바가 있고 이를 누르면 전체 페이지가 바뀌는게 아니라 아래 네비게이션 바는 그대로 두고 위의 내용만 바뀌는거다.

 


 

 

1) 뷰바인딩 활성화하기

이번 예제에서는 뷰 바인딩을 이용하여 프레그먼트를 구현해볼것이다.

안드로이드 아래에다 추가해주면 된다.

buildFeatures {
    viewBinding true
}

 

2)프레그먼트 만들어주고 꾸미기

프레그먼트 디렉토리 하나 만들어주고 빈 프레그먼트(원하는 갯수만큼) 만들어 주면 된다.

 

그런 다음 프레그먼트간 구분이 가게 해준다. 여기서는 꾸미기 귀찮으니까 첫째 두번쨰 세번쨰 이런식으로만 하겠음.

 

 

3) 뷰바인딩 이용하기

MainAcitivty.kt

package com.example.navigation

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.navigation.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private val binding : ActivityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
    }
}

binding 선언해주고 쓰면된다.

 

FirstFragment.kt

프레그먼트에 뷰 바인딩 적용하는 방법은 조금 다르다.

아래와 같이 해주면 된다

package com.example.navigation.Fragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.navigation.databinding.FragmentFirstBinding


class FirstFragment : Fragment() {
    private var _binding: FragmentFirstBinding? = null
    private val binding: FragmentFirstBinding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentFirstBinding.inflate(inflater, container, false)
        return binding.root
    }
    override fun onDestroyView() {
        _binding = null
        super.onDestroyView()
    }
}

 

이런식으로 모든 프레그먼트에 다 적용 해주면 된다.

 

4)이미지 파일 추가하기

 

drawable 파일 밑에 원하는 이미지를 추가해준다

 

안드로이드 스튜디오 자체적으로도 지원한다. 만약에 찾기 귀찮으면 저기서 적당한 이미지 파일 찾아서 쓰면 된다.

 

 

5)네비게이션 뷰 리소스 추가해주기

 

 

 

 

 

res/android resource file

리소스 아래에 안드로이드 리소스파일을 하나 만들어준다.

bottom_navigaiton_menu

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id="@+id/fragment_first"
        android:title="first"
        android:icon="@drawable/ic_first_24"/>
    <item android:id="@+id/fragment_second"
        android:title="second"
        android:icon="@drawable/ic_second_24"/>
    <item android:id="@+id/fragment_third"
        android:title="third"
        android:icon="@drawable/ic_third_24"/>
</menu>

바텀 네비게이션 메뉴에 아이템들을 추가해준다.

 

 

 

6)메인 엑티비티 꾸미기

 

 

activity_main.xml

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/frame_layout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottom_navigation_view"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation_view"
        android:layout_width="0dp"
        android:layout_height="56dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/bottom_navigation_menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

프레그먼트 공부하면서 알았는데 디자인에 관련된 표준안도 있었다. 

표준안에 따르면 네비게이션 바의 높이는 56dp 이다.

 

https://developer.android.com/guide/topics/ui/look-and-feel?hl=ko 

 

 

Android의 Material Design  |  Android 개발자  |  Android Developers

Android의 Material Design 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 머티리얼 디자인은 플랫폼 및 기기 전반의 시각적 요소, 모션 및 상호작용 디자인을 위

developer.android.com

 

 

7) 메인엑티비티에 기능 추가해주기

 

package com.example.navigation

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.navigation.Fragment.FirstFragment
import com.example.navigation.Fragment.SecondFragment
import com.example.navigation.Fragment.ThirdFragment
import com.example.navigation.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private val binding : ActivityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        setupBottomNavigationView()
    }

    private fun setupBottomNavigationView() {
        binding.bottomNavigationView.setOnItemSelectedListener { item ->
            when (item.itemId) {
                R.id.fragment_first -> {
                    supportFragmentManager.beginTransaction()
                        .replace(R.id.frame_layout, FirstFragment())
                        .commit()
                    true
                }
                R.id.fragment_second -> {
                    supportFragmentManager.beginTransaction()
                        .replace(R.id.frame_layout, SecondFragment())
                        .commit()
                    true
                }
                R.id.fragment_third -> {
                    supportFragmentManager.beginTransaction()
                        .replace(R.id.frame_layout, ThirdFragment())
                        .commit()
                    true
                }
                else -> false
            }
        }
    }


}

 

 

완성된 페이지

 


참고

https://www.inflearn.com/course/알기쉬운-modern-android

코틀린 기반으로 쓰여짐


1) What is?

Android의 Kotlin 코루틴  |  Android 개발자  |  Android Developers

 

Android의 Kotlin 코루틴  |  Android 개발자  |  Android Developers

Android의 Kotlin 코루틴 코루틴은 비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴입니다. 코루틴은 Kotlin 버전 1.3에 추가되었으며 다른 언어에서 확

developer.android.com

 

안드로이드 디밸로퍼에 따르면 코루틴이란 비동기적으로 실행되는 코드를 간소화하기 위한 동시 실행 설계 패턴이다.

그래서 그게 뭔데? 

코루틴은 CO(협동,같이하다)+ROUTINE(루틴)의 합성어다 

코루틴의 가장 큰 특징이라함은 출구와 입구가 여러개라는 것이다.

일반적인 서브루틴의 경우에는 중단이라는 의미가 존재하지 않는다 그렇지만 코루틴은 가능하다.

 

 

 

[Kotlin] Coroutine - Basics - 카미유 테크 블로그 (june0122.github.io)

 

 

또 구글 코드랩은 아래와 같이 서술한다.

https://developer.android.com/codelabs/kotlin-coroutines#0

즉 코루틴의 핵심은, 비동기적 콜백을 순차적 코드로 변환해준다는것이다.

 


**여기서 잠깐,비동기 출력이란?

특정 로직의 실행이 끝날때까지 기다려주지 않고 나머지 코드를 먼저 실행하는것을 비동기 처리라고 한다.

 

 

https://ko.wikipedia.org/wiki/%EB%B9%84%EB%8F%99%EA%B8%B0_%EC%9E%85%EC%B6%9C%EB%A0%A5

 

2) 실전예제

우선 나는 코루틴의 ㅋ 도 모르기 때문에

https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine

 

Coroutines basics | Kotlin

 

kotlinlang.org

도움을 받아서 그대로 따라할것이다.

 

 

1) 코루틴 만들기

 

package com.example.coroutine

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch { // launch a new coroutine and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello") // main coroutine continues while a previous one is delayed
}

 

코루틴을 공부하고 있는 입장이라면 아 출력값이 WORLD! HELLO 일리는 없겠구나 하고 대충 예상할 수 있지만

어쨌든간에 돌려보면

 

결과값

잘 나온다.

 

여기서 각각 요소들의 역할은 뭔가?

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

// --------------- async ---------------

 

 

사실 뭔지 잘 모르겠지만 return 값 자체가 coroutine이니까 코루틴을 만들어주는거구나. 정도로 이해했다.

 

launch is a coroutine builder. It launches a new coroutine concurrently with the rest of the code, which continues to work independently. That's why Hello has been printed first.

 

delay is a special suspending function. It suspends the coroutine for a specific time. Suspending a coroutine does not block the underlying thread, but allows other coroutines to run and use the underlying thread for their code.

(코루틴을 특정 시간동안 연기 시켜준다.  코루틴의 연기(Suspending)는 근본적인 쓰레드를 차단하지는 않지만, 다른 코루틴들의 동작을 허락하고 근본적인 쓰레드를 그들의 코드에 이용한다. )

 

runBlocking is also a coroutine builder that bridges the non-coroutine world of a regular fun main() and the code with coroutines inside of runBlocking { ... } curly braces. This is highlighted in an IDE by this: CoroutineScope hint right after the runBlocking opening curly brace.

 

If you remove or forget runBlocking in this code, you'll get an error on the launch call, since launch is declared only in the CoroutineScope:

 

Coroutines can be thought of as light-weight threads

코루틴은 경량 쓰레드로 생각할 수 있다. 이에 관하여 예제를 살펴보자면

 

fun main() = runBlocking {
    repeat(100_000) { // launch a lot of coroutines
        launch {
            delay(5000L)
            print(".")
        }
    }
}

 

컴퓨터 상태마다 다르겠지만 하나도 버벅임이 없다.

thread로 비교한번 해보자면

 fun main() = runBlocking {
        repeat(100_000) { // launch a lot of coroutines
            thread {
                Thread.sleep(5000L)
                print(".")
            }
        }
    }

 

확실히 차이가 있다!

 


 

 

fun main() = runBlocking { // this: CoroutineScope
        GlobalScope.launch { // launch a new coroutine and continue
            delay(3000L) // non-blocking delay for 3 second (default time unit is ms)
            println("World!") // print after delay
        }
        println("Hello") // main coroutine continues while a previous one is delayed
        delay(2000L)
    }
}

 

 

만약 위와 같은 코드가 있다면 어떤 결과를 낳을까?

결과값은 아마도 Hello! 만 뜰것이다

왜냐면 코루틴 자체가 3초뒤에 결과값을 출력하고 싶은데 전체가 2초뒤면 끝나기 때문이다. 

그럼 얘를 어떤식으로 해결 할 수 있을까?

 

launch를 하게되면 job을 반환하게 되는데

fun main() = runBlocking { // this: CoroutineScope
    val job =GlobalScope.launch { // launch a new coroutine and continue
        delay(3000L) // non-blocking delay for 3 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello") // main coroutine continues while a previous one is delayed
    job.join()
}

 

위와 같은 결과값을 나타낸다. join은 코루틴이 완료 된 후에 메인함수가 종료 되게끔 만들어준다.

근데 위와같은 방법은 문제가 많다. 가장 대표적으로는 앞으로 수많은 코루틴을 쓰게 될텐데

그때마다

val job = glbalscope~~~ 

val2 job = globalscop~~

이런식으로 관리를 해야하는데, 이는 굉장히 번거롭고 귀찮은 방법이다.

 

 

그렇다면 조금 더 세련된 방법은 없을까? 이에 관해 구글은  structured concurrency 라는것을 제공한다

structured concurrency 란?

 

Coroutines follow a principle of structured concurrency which means that new coroutines can be only launched in a specific CoroutineScope which delimits the lifetime of the coroutine. The above example shows that runBlocking establishes the corresponding scope and that is why the previous example waits until World! is printed after a second's delay and only then exits.

 

그래서 어떻게하는데? 방법은 간단하다!

 

fun main() = runBlocking { // this: CoroutineScope
        this.launch {
            delay(2000L)
            println("WORLD!")
        }
        this.launch {
            delay(1000L)
            println("World!")
        }
        println("Hello!,")

    }

 

 

크게 달라진건 없다! 근데 join이 없는데도 결과값은 잘 나온다!

 

 

 

그렇다면 핵심은?

TOP-LEVEL에 코루틴을 만들지 말고 코루틴의 자식들에 코루틴을 만들어라

왜?? 부모 코루틴이 자식 코루틴이 완료 될때까지 기다려주기 때문에 관리하기가 훨씬 수월하다!

 

이렇게 진행하다보니 특정 함수를 추출해서 따로 관리하고 싶어졌다 그래서

 

 fun main() = runBlocking { // this: CoroutineScope
        launch {
            myworld()
        }

        println("Hello!,")

    }
    fun myworld() {
        delay(1000L)
        println("world.")
    }
}

 

요런식으로 해보니

 

코루틴안에서나 서스펜드 함수안에서만 불려야 한다고 오류가 뜬다.

그래서 앞에 suspend 키워드를 붙여주니 잘된다.

fun main() = runBlocking { // this: CoroutineScope
        launch {
            myworld()
        }

        println("Hello!,")

    }
    suspend fun myworld() {
        delay(1000L)
        println("world.")
    }

https://kotlinworld.com/144

 

[Coroutine] 5. suspend fun의 이해

일시중단 가능한 코루틴 코루틴은 기본적으로 일시중단 가능하다. launch로 실행하든 async로 실행하든 내부에 해당 코루틴을 일시중단 해야하는 동작이 있으면 코루틴은 일시 중단된다. 예시로

kotlinworld.com

관련해서 매우매우 좋은 포스팅이 있어 올린다.

 


참고

 

 

 

 

 

 

본 포스팅은 각종 인터넷과 유튜브에서 올라온 자료를 재구성한것으로 틀릴 가능성이 다분히 있음.

코틀린 기반으로 쓰여짐


 

 

1.Modern Android Development?

Modern Android Development  |  Android 개발자  |  Android Developers

 

Modern Android Development  |  Android 개발자  |  Android Developers

Android 개발자를 위한 공식 사이트입니다. 앱 개발자 및 디자이너를 위한 Android SDK 및 문서를 제공합니다.

developer.android.com

 

Modern Android Development 는

 

 

Android팀에서 권장하는 개발 도구, API, 언어, 배포 기술을 통해 개발자는 생산성을 높이고 수많은 기기에서 실행되는 더 나은 앱을 만들 수 있습니다.

라고 안드로이드 디밸로퍼에 서술되어 있다.

 

근데 그래서 이게뭔데? 

쉽게 얘기하면 안드로이드 앱 번들 + 제트팩 + 코틀린 + 안드로이드 스튜디오를 합쳐서 일컫는 말이다.

오늘부터 포스팅할 내용은 앱 번들을 제외한 안드로이드 모던 디밸로퍼의 모든 내용들을 공부할 것이다.

코틀린과 안드로이드 스튜디오는 사실 필수이고, 주된 내용은 제트팩에 관한 얘기일 것이다.

 


2.Architecture Pattern

제트팩에 관해 공부하기전에 우리는 아키텍쳐 패턴에 관하여 알 필요가 있다.

아키텍쳐 패턴은 디자인 패턴의 상위 개념이라고 말할 수 있다.혹시나 아키텍쳐 패턴, 디자인 패턴이 무엇인지 모르는 사람들을 위하여 간단하게 서술하자면

 

주어진 상황의 소프트웨어 구조에서 발생하는 문제점을 해결하기 위한 일반화된 재사용 가능한 솔루션

정도로 정의할 수 있다. 대표적인 아키텍쳐 패턴으로는

계층화패턴, 마스터-슬레이브 패턴, 파이프-필터 패턴,브로커 패턴 등 엄청나게 많지만 

모바일 환경에서는 대표적으로 3가지가 있다.

 

1)MVC

Model + View + Controller

  • Model  어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분
  • View    사용자에게 보여지는 부분 (UI)
  • Controller 사용자의 입력을 받고 처리하는 부분

MVC 패턴의 장점은 널리 사용되고 있는 패턴이라는 점에 걸맞게 가장 하다. 보편적으로 많이 사용되는 디자인패턴이다.

MVC 패턴의 단점은 View와 Model 사이의 의존성이 높다는 것이다. View와 Model의 높은 의존성은 어플리케이션이 커질 수록 복잡하지고 유지보수가 어려워 진다.

 

2)MVP

Model + View + Presenter

  • Model       어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분
  • View         사용자에서 보여지는 UI 부분
  • Presenter  View에서 요청한 정보로 Model을 가공하여 View에 전달해 주는 부분

MVP 패턴의 장점은 View와 Model의 의존성이 없다는 것이다.

반면에 View와 Presenter 사이의 의존성이 높아 지는 단점이 있다.

 

 

3)MVVM

 Model + View + View Model

  • Model  어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분
  • View    사용자에게 보여지는 UI
  • View Model : View를 표현하기 위해 만든 View를 위한 Model

사실 MVVM 모델에서 아직도 제대로 이해하고 있는지 애매한 부분이 뷰 모델이다. 뷰란? 사용자에게 보여지는 UI 근데 사용자에게 보여지는 UI를 표현하기 위해 만든 뷰를 위한 모델?? 이 부분은 앞으로 공부하면서 차차 해결해 나가리라 생각한다.

 

 

 

MVVM 패턴은 Command 패턴과 Data Binding 두 가지 패턴을 사용하여 구현,View와 Model 사이의 의존성이 없다.

 View Model 사이의 의존성 또한 없앤 디자인패턴입니다. 각각의 부분은 독립적이기 때문에 모듈화 하여 개발가능하지만

MVVM 패턴의 단점은 View Model의 설계가 쉽지 않다는 점이 큰 단점이다.

 


3.Support Library

지원 라이브러리  |  Android 개발자  |  Android Developers

 

지원 라이브러리  |  Android 개발자  |  Android Developers

지원 라이브러리 참고: Android 9.0(API 수준 28)의 출시와 함께 Jetpack의 일부인 새로운 버전의 지원 라이브러리 AndroidX가 제공됩니다. AndroidX 라이브러리는 기존 지원 라이브러리를 포함하며 최신 Jetp

developer.android.com

지원 라이브러리는 몇 가지 뚜렷한 용도가 있습니다. 플랫폼의 이전 버전에 대한 하위 호환성 클래스는 그중 하나입니다. 

 

 

 

라고 안드로이드 디밸로퍼에 서술되어 있다. 쉽게 설명하면 api버전이 낮아서 지원 하지 않는 신규 기능도 지원 라이브러리를 통해서 쓸 수 있는것이다. 대표적으로 리사이클러뷰가 있다.

여기까지만 들으면 굉장히 좋은건데 이게 왜? 라고 할 수 있다. 이 지원라이브러리의 가장 큰 문제는 17년 구글에서 발표한What's new in Android Support Library에 따라 api버전 14부터 지원하게 되면서 문제가 커졌다. 

What's new in Android Support Library (Google I/O '17) - YouTube

지원 라이브러리를 임포트하다보면implementation com.android.support:appcompat-v7 ~~ 뭐 이런식으로 v가 붙은게 있다.이게 무슨뜻이냐? api 7부터 지원 가능하다는 뜻이다.근데 구글측에서 너무 오래된 기기의 사용을 막으려고 적혀진 버전과 상관없이 api 14부터 지원하게 된것이다.지원 라이브러리는 많은 라이브러리가 통합되어있기에 내가 사용하지 않는 기능도 포함된다. 이는 굉장히 비효율적이다.

 

이러한 비효율을 해결하기 위해,안드로이드 지원 라이브러리를 안드로이드 익스텐션 라이브러리로 바꿈과 동시에 제트팩을 도입하게 된다.

(38) Android Jetpack: What’s new in Android Support Library (Google I/O 2018) - YouTube

(38) Android Jetpack: What’s new in Android Support Library (Google I/O 2018) - YouTube

주요 내용은 기존에는 a가 업데이트 되어도 전체를 업데이트 해야했지만 이제는 a만 업데이트가 가능하다.

내용 자체가 워낙 방대하고 나도 다 몰라서, 동영상 보기를 추천한다.

 


4.Android JetPack

그래서 제트팩이 뭔데?

드디어 오늘 얘기할 본 내용이다.

 

Jetpack은 개발자가 관심 있는 코드에 집중할 수 있도록 권장사항 준수, 상용구 코드 축소, 모든 Android 버전 및 기기에서 일관되게 작동하는 코드 작성을 돕는 라이브러리 모음입니다.

 

사실 이렇게 얘기하면 알 수 없다. 오늘부터 안드로이드 디밸로퍼 다큐멘테이션을 따라서 하나하나 이것이 무엇인지 알아가보려고 한다.


참고

Android Jetpack 시작하기  |  Android 개발자  |  Android Developers

 

Android Jetpack 시작하기  |  Android 개발자  |  Android Developers

Jetpack에 기반해 간단한 앱을 빌드하는 방법을 알아보세요.

developer.android.com

Support library와 Androidx와 Jetpack::Android Studio에서 Kotlin으로#39 - YouTube

Support Library와 Androidx와 Jetpack (cliearl.github.io)

 

Support Library와 Androidx와 Jetpack

Support Library Support Library의 필요성 구글에서는 Android API의 하위 호환성 문제를 해결하기 위해 Support Library를 만들었습니다. 예를들어 롤리팝(API 21)부터 도입된 RecyclerView는 API 21 이하의 기기에서

cliearl.github.io

 

코틀린 기반으로 쓰여짐


 

리사이클러뷰를 완벽 이해하려고 써보는 리사이클러뷰 포스팅

 

물론 틀린점 있을거다. 적극 피드백 환영

 

https://developer.android.com/jetpack/androidx/releases/recyclerview?hl=ko 

 

RecyclerView  |  Android 개발자  |  Android Developers

RecyclerView 메모리 사용량을 최소화하면서 UI에 많은 양의 데이터를 표시합니다. 이 표에는 androidx.recyclerview 그룹의 모든 아티팩트가 나열되어 있습니다. 이 라이브러리는 2022년 6월 29일에 최종 업

developer.android.com

 


리사이클러뷰란?

출처 - 안드로이드 디밸로퍼

그렇다. 리사이클러뷰란 메모리 사용량을 최소화 하면서 많은 양의 데이터를 표시하기 위한 도구이다.

그 전에 많이 쓰이던 리스트 뷰는 새로운 데이터가 필요할 때마다 기존에 있던 데이터는 삭제하고 새로운 데이터를 만드는 형식이였는데

리사이클러뷰는 기존에 있던 데이터를 재 활용해서 데이터의 낭비를 최소화 하는데 중점을 두었다.

아래 그림이 굉장히 잘 설명했다고 생각한다. 

또 오늘 만들어볼 것은 뷰바인딩을 이용한다. 고로 뷰 바인딩은 기본적으로 알고 있어야 한다!

고로 모른다면 가서 게시글 한번 보고 오기를 추천한다.

 

https://todaycode.tistory.com/29

 

안드로이드 뷰 바인딩(view binding)

1. 뷰 바인딩  1-1. 라떼는 말이야...  1-2. 변천사  1-3. findViewById와의 차이점 2. 사용법  2-1. gradle 추가  2-2. 액티비티  2-3. 프래그먼트  2-4. viewBindingIgnore 1. 뷰 바인딩 1-1. 라떼는 말..

todaycode.tistory.com

 

https://velog.io/@5y145/RecyclerView


 

각설하고 바로 만들어 보겠다.

 

1) 

틀을 만들어준다.

 

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/todo_list"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/clear_btn"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:itemCount="3" //미리볼 갯수!
        tools:listitem="@layout/item_todo"> //이렇게 하면 아이템 리스트들의 미리보기를 지원한다.


    </androidx.recyclerview.widget.RecyclerView>

    <Button
        android:id="@+id/clear_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="clear all"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/todo_list"
        app:layout_constraintStart_toStartOf="parent">

    </Button>
</androidx.constraintlayout.widget.ConstraintLayout>

 

2. 리사이클러뷰들의 아이템을 구성한다

 

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="100dp">

    <ImageView
        android:src="@drawable/ic_launcher_background"
        android:id="@+id/image_IV"
        android:layout_width="40dp"
        android:layout_height="40dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/todo_title_text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </ImageView>

    <TextView
        android:id="@+id/todo_title_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/completed_check_box"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="do something">

    </TextView>

    <CheckBox
        android:id="@+id/completed_check_box"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </CheckBox>

</androidx.constraintlayout.widget.ConstraintLayout>

3. 데이터 클래스를 만들어준다.

 

Todo.kt

 

package com.example.todolist



data class Todo(
    val title : String,
    var completed : Boolean,


    )

 

4. 어댑터를 만들어준다

package com.example.todolist

import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.todolist.databinding.ItemTodoBinding


class TodoAdapter(private val todos:List<Todo>):RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {

    fun clearAll() {
        todos.forEach {
            it.completed=true
        }
        notifyDataSetChanged() //데이터 변경 됐으니까다시 호출해!
    }


   // 호출되는 횟수가 정해짐
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
       Log.d("TAG","onCreateViewHolder")
       val binding = (ItemTodoBinding.inflate(LayoutInflater.from(parent.context),parent,false))
       return TodoViewHolder(binding).also {
           binding.completedCheckBox.setOnCheckedChangeListener { compoundButton, b ->
               Log.d("TAG","isChecked")
               todos.getOrNull(it.adapterPosition)?.completed = b

           }
       }

    }
  // 스크롤을 할때 호출됨.
    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
      Log.d("TAG","onBindViewHolder $position")
        holder.bind(todos[position])

    }

    override fun getItemCount(): Int= todos.size //아이템의 크기


    class TodoViewHolder(private val binding:ItemTodoBinding) :
        RecyclerView.ViewHolder(binding.root) {

            fun bind(todo:Todo) {
                binding.todoTitleText.text = todo.title
            // 재활용되는지 확인해보자.
              binding.completedCheckBox.isChecked = todo.completed

            }

    }
}

 

여기서 중요 포인트가 하나 있다.

onCreateViewHolder 에서 

  val binding = (ItemTodoBinding.inflate(LayoutInflater.from(parent.context),parent,false)) 

여기서 첫번째 파라미터로 context를 받아와야하는데 context는 주어지지 않는다.

그래서 간혹 Adapter에

class TodoAdapter(private val todos:List<Todo>,Context context) 이런식으로 주는 경우가 있는데

그럴 필요 없을 뿐더러 그러면 안된다.

Viewgroup은 view를 상속받았고 이미 context를 가지고 있다.

parent에 이미 context가 있음을 명심하자!

 

5. 연결해주기

MainAcitivty.kt

package com.example.todolist

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.todolist.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding : ActivityMainBinding

    private val todos = listOf(
        Todo("list1",false),
        Todo("list2",false),
        Todo("list3",false),
        Todo("list4",false),
        Todo("list5",false),
        Todo("list6",false),
        Todo("list7",false),
        Todo("list8",false),
        Todo("list9",false),
        Todo("list10",false),
        Todo("list11",false),
        Todo("list12",false),
        Todo("list13",false),
        Todo("list14",false),
        Todo("list15",false),
        Todo("list16",false),
        Todo("list17",false),
        Todo("list18",false),
        

    )
    

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)


        initializeViews()
    }

    private fun initializeViews() {

        binding.todoList.layoutManager = LinearLayoutManager(this)
        binding.todoList.adapter = TodoAdapter(todos)
        binding.clearBtn.setOnClickListener{

            (binding.todoList.adapter as TodoAdapter)?.clearAll()

        }

    }
}

 

 

실제로 이렇게 하드코딩하는 경우는 없겠지만 데이터베이스에서 가져오는건 나중에 따로 다루도록 하겠다!

 

 

6. 결과

 

 

 

하나 주목해서 봐야할껀 로그캣이다!

2022-08-22 21:36:35.663 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:35.688 9309-9309/com.example.todolist D/TAG: onBindViewHolder 0
2022-08-22 21:36:35.690 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:35.693 9309-9309/com.example.todolist D/TAG: onBindViewHolder 1
2022-08-22 21:36:35.696 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:35.699 9309-9309/com.example.todolist D/TAG: onBindViewHolder 2
2022-08-22 21:36:35.701 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:35.704 9309-9309/com.example.todolist D/TAG: onBindViewHolder 3
2022-08-22 21:36:35.706 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:35.710 9309-9309/com.example.todolist D/TAG: onBindViewHolder 4
2022-08-22 21:36:37.316 9309-9309/com.example.todolist D/TAG: isChecked
2022-08-22 21:36:38.215 9309-9309/com.example.todolist D/TAG: isChecked
2022-08-22 21:36:38.981 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:38.990 9309-9309/com.example.todolist D/TAG: onBindViewHolder 5
2022-08-22 21:36:39.163 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:39.169 9309-9309/com.example.todolist D/TAG: onBindViewHolder 6
2022-08-22 21:36:39.400 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:39.405 9309-9309/com.example.todolist D/TAG: onBindViewHolder 7
2022-08-22 21:36:40.201 9309-9309/com.example.todolist D/TAG: onCreateViewHolder
2022-08-22 21:36:40.206 9309-9309/com.example.todolist D/TAG: onBindViewHolder 8
2022-08-22 21:36:40.336 9309-9309/com.example.todolist D/TAG: onBindViewHolder 9
2022-08-22 21:36:40.338 9309-9309/com.example.todolist D/TAG: isChecked
2022-08-22 21:36:40.532 9309-9309/com.example.todolist D/TAG: onBindViewHolder 10
2022-08-22 21:36:40.535 9309-9309/com.example.todolist D/TAG: isChecked
2022-08-22 21:36:41.290 9309-9309/com.example.todolist D/TAG: onBindViewHolder 11
2022-08-22 21:36:41.370 9309-9309/com.example.todolist D/TAG: onBindViewHolder 12
2022-08-22 21:36:41.534 9309-9309/com.example.todolist D/TAG: onBindViewHolder 13
2022-08-22 21:36:42.335 9309-9309/com.example.todolist D/TAG: onBindViewHolder 14
2022-08-22 21:36:42.398 9309-9309/com.example.todolist D/TAG: onBindViewHolder 15
2022-08-22 21:36:42.499 9309-9309/com.example.todolist D/TAG: onBindViewHolder 16
2022-08-22 21:36:43.197 9309-9309/com.example.todolist D/TAG: onBindViewHolder 17
2022-08-22 21:36:45.398 9309-9309/com.example.todolist D/TAG: isChecked
2022-08-22 21:36:45.998 9309-9309/com.example.todolist D/TAG: isChecked

 

oncreateViewHolder가 일정 데이터가 지난후에는 더 이상 소환되지 않는다

왜? 재활용하기 때문에 더이상 create할 필요는 없어서이다.

 


참고

https://www.youtube.com/watch?v=RYM2H0Qzq9I 

 

+ Recent posts