Today's

길을 나서지 않으면 그 길에서 만날 수 있는 사람을 만날 수 없다

모바일 앱(안드로이드)

안드로이드 앱 만들기 : Jetpack Compose 에서 프로필 이미지 저장해 보기

Billcorea 2022. 5. 14. 09:58
반응형

이미지 저장 예시

앱을 만들다 보면 프로필 가져오기 기능을 구현해 보는 경우가 간혹 생긴 게 된다. 

 

오늘은 compose을 이용한 구현을 하는 과정에서 

갤러리에서 이미지를 불러와서 프로필 사진으로 저장하는 과정을 구현해 보고자 한다.

 

그림과 같이 구현해 볼 예정이다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

전체 소스의 일부는 아래와 같이 구현이 되었다.

    @Composable
    private fun mainContent(padding: Modifier) {
        
        Column(modifier = Modifier
            .fillMaxHeight()
            .fillMaxWidth()
            .padding(16.dp),
            verticalArrangement = Arrangement.Top,
            horizontalAlignment = Alignment.Start
        ) {
            var imageUri by remember {
                mutableStateOf<Uri?>(null)
            }
            var imageTy by remember {
                mutableStateOf<Boolean>(false)
            }
            val context = LocalContext.current
            val bitmap =  remember {
                mutableStateOf<Bitmap?>(null)
            }
            val launcher = rememberLauncherForActivityResult(contract =
            ActivityResultContracts.GetContent()) { uri: Uri? ->
                imageUri = uri
            }

            Card(modifier = Modifier
                .fillMaxWidth()
                .height(150.dp)
                .padding(8.dp)
                .border(1.dp, Color.Gray),
                content = {
                    Text(getString(R.string.profileImage))
                    Row (
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.End
                    )
                    {
                        imageUri?.let {
                            if (Build.VERSION.SDK_INT < 28) {
                                bitmap.value = MediaStore.Images
                                    .Media.getBitmap(context.contentResolver,it)
                            } else {
                                val source = ImageDecoder
                                    .createSource(context.contentResolver,it)
                                bitmap.value = ImageDecoder.decodeBitmap(source)
                            }
                            bitmap.value?.let {  btm ->
                                val baos = ByteArrayOutputStream()
                                btm.compress(
                                    Bitmap.CompressFormat.PNG,
                                    100,
                                    baos
                                )
                                val b: ByteArray = baos.toByteArray()
                                val encoded: String = Base64.encodeToString(b, Base64.DEFAULT)
                                editor.putString("profileImage", encoded)
                                editor.commit()
                                Image(bitmap = btm.asImageBitmap(),
                                    contentDescription = "profile",
                                    contentScale = ContentScale.Crop,
                                    modifier = Modifier
                                        .clip(shape = RoundedCornerShape(16.dp))
                                        .size(150.dp, 250.dp))
                            }
                        }
                        if (!"".equals(sp.getString("profileImage","")) && !imageTy) {
                            var encoded = sp.getString("profileImage","")
                            val imageAsBytes: ByteArray =
                                Base64.decode(encoded?.toByteArray(), Base64.DEFAULT)
                            var bitMap = BitmapFactory.decodeByteArray(imageAsBytes, 0, imageAsBytes.size)
                            Image(bitmap = bitMap.asImageBitmap(),
                                contentDescription = "profile",
                                contentScale = ContentScale.Crop,
                                modifier = Modifier
                                    .clip(shape = RoundedCornerShape(16.dp))
                                    .size(150.dp, 250.dp))
                        }
                        Spacer(modifier = Modifier.padding(10.dp))
                        IconButton(onClick = {
                            imageTy = true
                            launcher.launch("image/*")
                        }) {
                            Icon(imageVector = Icons.Default.PhotoAlbum, contentDescription = "Search Profile", tint = Color.Blue)
                        }
                    }
                })
            Text(getString(R.string.title_translate_ty))
            Spacer(modifier = Modifier.padding(10.dp))
            Card(modifier = Modifier
                .fillMaxWidth()
                .height(60.dp)
                .padding(8.dp)
                .border(1.dp, Color.Gray),
                content = {
                    Row (verticalAlignment = Alignment.CenterVertically)
                    {
                        if (isTranslate.value) {
                            Text(text = getString(R.string.msgAutoTranslate))
                        } else {
                            Text(text = getString(R.string.msgTranslateNo))
                        }
                        Switch(checked = isTranslate.value, onCheckedChange = {
                            isTranslate.value = it
                        })
                    }

                })
            Spacer(modifier = Modifier.padding(10.dp))
            Row (verticalAlignment = Alignment.CenterVertically)
            {
                Text(getString(R.string.title_master_language))
            }
            Spacer(modifier = Modifier.padding(10.dp))
            Card(modifier = Modifier
                .fillMaxWidth()
                .height(60.dp)
                .padding(8.dp)
                .border(1.dp, Color.Gray),
                content = {
                    if (languages.size > 0) {
                        DropdownDemo()
                    }
                })
        }
    }

여기서 살펴 보아야 하는 부분은 다음과 같다.

 

갤러리에 있는 이미지를 불러오는 실행은 launcher을 호출해서 실행을 하고 있고.

launcher.launch("image/*")

 

실행된 결과를 아래와 같은 코드 구현을 통해서 bitmap 이미지를 변환 하여 화면에 Image에 속성을 넣어주고 있는 것을 확인할 수 있었다.

                       imageUri?.let {
                            if (Build.VERSION.SDK_INT < 28) {
                                bitmap.value = MediaStore.Images
                                    .Media.getBitmap(context.contentResolver,it)
                            } else {
                                val source = ImageDecoder
                                    .createSource(context.contentResolver,it)
                                bitmap.value = ImageDecoder.decodeBitmap(source)
                            }
                            bitmap.value?.let {  btm ->
                                val baos = ByteArrayOutputStream()
                                btm.compress(
                                    Bitmap.CompressFormat.PNG,
                                    100,
                                    baos
                                )
                                val b: ByteArray = baos.toByteArray()
                                val encoded: String = Base64.encodeToString(b, Base64.DEFAULT)
                                editor.putString("profileImage", encoded)
                                editor.commit()
                                Image(bitmap = btm.asImageBitmap(),
                                    contentDescription = "profile",
                                    contentScale = ContentScale.Crop,
                                    modifier = Modifier
                                        .clip(shape = RoundedCornerShape(16.dp))
                                        .size(150.dp, 250.dp))
                            }

이런 코드를 Kotlin을 통해서 구현을 한다고 하면 아마 다음과 같은 구현이 될 것 같다. 

 

 

intent을 통해서 갤러리에 있는 이미지를 열어 오는 구현을 하고...

Intent(Intent.ACTION_GET_CONTENT).apply {
            type = "image/*"
            startActivityForResult(
                Intent.createChooser(this, "Get Album"),
                REQ_SELECT_IMG
            )
        }

 

onActivityResult을 통해서 받아온 이미지를 처리하는 모양으로 구현이 될 것 같은데...

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        super.onActivityResult(requestCode, resultCode, intent)
        
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                REQ_SELECT_IMG -> {   
                
                    val currentImageUri = intent?.data ?: return //이미지 URL
                    
                    val needAdjust = true
                    if (needAdjust) {
                        setAdjImgUri(currentImageUri) //방법 2
                    } else {
                        setImgUri(currentImageUri) //방법 1
                    }
                }
           
            }
        }
    }

위에서 구현된 코드와 비교를 해 보면 훨씬 간략해졌음을 느끼게 된다. 

 

 

java 코드로 구현된 예시는 아래 링크를 참고해 보면 좋을 것 같다.

https://billcorea.tistory.com/40

 

안드로이드 앱 만들기 갤러리 에서 이미지 받아오기 (자동회전 방지)

앱을 만들다가... 갤러리에서 이미지 받아오는 와서 사용하는 것을 구현하고 있는 중인데... 사진이 돌아간다. 흑~ 그래서 구글링 신에서 질문을 했다... 답... Exif 을 구현해서 사진을 돌리는 코드

billcorea.tistory.com

 

또한 compose에서 구현은 아래 site에서 참고했음을 밝힌다.

https://yjyoon-dev.github.io/android/2022/04/09/android-05/

 

[Android] 서버에 이미지 업로드하기(feat. Android 10, Compose)

Android 10 이상에서 Jetpack Compose를 통해 기기의 이미지를 선택한 뒤 서버에 업로드해보는 과정을 알아보자

yjyoon-dev.github.io

 

반응형