반응형 웹이 필요했다.

평소에는 아무것도 보이지 않다가 작아지면 토글버튼이 생기고

토글 버튼 누르면 메뉴가 아래로 갱신되는 형태이다.

 

그러려면 자바스크립트를 쓸 수 밖에 없었고,

자바 스크립트를 사용하는 방법은 다음과 같았다.

 

@inherits LayoutComponentBase

<PageTitle>Changed</PageTitle>

<div class="wrapper">

    <header class="common-header">

    </header>

    <main>
        <article class="class-main">
            @Body
          
        </article>
    </main>

    <footer class="common-footer">

    </footer>
</div>

MainLayout.razor

 

@page "/"


<nav class="navbar">
    <div class="navbar__logo">
        <i class="fab fa-apple"></i>
        <a href="">자바스크립트 넣기</a>
    </div>
    <ul class="navbar__menu">
        <li><a href="">1</a></li>
        <li><a href="">2</a></li>
        <li><a href="">3</a></li>
        <li><a href="">4</a></li>
        <li><a href="">5</a></li>
    </ul>
    <ul class="navbar__links">
        <li> <i class="fab fa-facebook-square"></i></li>
        <li><i class="fab fa-instagram"></i></li>
    </ul>

    <button href="#" class="navbar__toggleBtn" @onclick="ToggleMenu"></button>
</nav>

@code {
    
}

index.razor

 

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace Changed.Pages
{
    public partial class Index
    {
        [Inject]
        public IJSRuntime? JSRuntime { get; set; }

        private IJSObjectReference _jsModule;
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                _jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./assets/js/main.js");
            }
        }
        public async Task ToggleMenu() =>
            await _jsModule.InvokeVoidAsync("Changed");


    }
}

index.razor.cs

return 값이있다면 

```InvokeAsynce<object>```를

return 값이 없다면

```InvokeVoidAsync()```를

쓰면된다.

 

export function Changed() {
    const toggleBtn = document.querySelector('.navbar__toggleBtn');
    const menu = document.querySelector('.navbar__menu');
    const links = document.querySelector('.navbar__links');

    toggleBtn.addEventListener('click', () => {
        menu.classList.toggle('active');
        links.classList.toggle('active');
    })
    

}

main.js

_Host.cstml에 작성하면 export를 따로 적지 않아줘도된다.

 

결과

 

 

버튼 누르기전

 

버튼 누른후

 

 

 

참고

ASP.NET Core Blazor의 .NET 메서드에서 JavaScript 함수 호출 | Microsoft Learn

 

ASP.NET Core Blazor의 .NET 메서드에서 JavaScript 함수 호출

Blazor 앱의 .NET 메서드에서 JavaScript 함수를 호출하는 방법을 알아봅니다.

learn.microsoft.com

[Blazor] javascript import 방법 - 👨‍🏫 튜토리얼, 팁, 강좌 - 닷넷데브 (dotnetdev.kr)

 

[Blazor] javascript import 방법

📌 해결책은 맨 마지막에 나와있습니다. 🗒시행착오를 겪으면서 찾아본 문서 https://learn.microsoft.com/ko-kr/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-6.0 문서에서 나온대로 따라하면 제대로

forum.dotnetdev.kr

반응형 헤더 만들기 (velog.io)

 

반응형 헤더 만들기

개발 유튜버 '드림코딩 by 엘리' 님의 영상, "웹사이트 따라만들기, 반응형 헤더편"을 보고 기본적인 반응형 헤더를 만들어보자.

velog.io

 

코틀린 기반으로 쓰여짐


 

 

리스트 어댑터를 활용한 리사이클러뷰를 만들었는데 이상하게 값이 갱신이 안되는것이다.

좀 더 정확히는 뒤로가기와 같은 리로드를 해야 갱신이 되었다.

이상하다.. 분명히 다른거 할떄는 잘됐는데 뭐가 문제지 하고 절망에 빠져있는 찰나에 나와 비슷한 증상을 가진사람들을 발견했다

https://stackoverflow.com/questions/49726385/listadapter-not-updating-item-in-recyclerview

 

ListAdapter not updating item in RecyclerView

I'm using the new support library ListAdapter. Here's my code for the adapter class ArtistsAdapter : ListAdapter<Artist, ArtistsAdapter.ViewHolder>(ArtistsDiff()) { override fun

stackoverflow.com

 

결론부터 얘기하면 비동기적으로 데이터를 받아올때 발생할 수 있는 문제이다.

주소값이 같은 경우에는 갱신하지 않기때문이다.

앱 내에서 참조가 변경되지 않는(주소값이 같은) 하나의 리스트를 유지하면서 값이 변경 될 경우 submitList 함수 호출 시 주소가 다른 리스트를 넘겨주어야 한다.

 

 

결론 그냥 submitList할때 tolist() 로 바꿔주면됨. 깊은 복사를 해야하나 생각하고 있었는데 그럴필요 없어 다행이다.

myRecyclerViewAdapter.submitList(boardDataList.toList())

 

 


해결하고 나니 보이는 글들.. 한글로도 찾아보는 습관을 들이자

참고

https://bb-library.tistory.com/257

 

[안드로이드] ListAdapter의 작동 원리 및 갱신이 안되는 경우

개요 RecyclerView를 활용하여 목록을 리스팅할 때 흔히 사용하는 어답터로 RecyclerView.Adapter와 ListAdapter로 나뉜다. 전자는 아이템 목록을 직접 관리하며 값이 변경될 경우 변경된 범위, 항목에 대해

bb-library.tistory.com

 

코틀린 기반으로 쓰여짐


 

 

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

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

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

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

 


참고

 

 

 

 

 

 

+ Recent posts