와챠의 우당탕탕 코딩 일기장
[Android/Kotlin] S3 image upload/순서대로 여러장 업로드하기/RxKotlin/MultiUploaderS3Client 본문
[Android/Kotlin] S3 image upload/순서대로 여러장 업로드하기/RxKotlin/MultiUploaderS3Client
minWachya 2022. 9. 17. 16:18오늘 해볼 것:
1. 사용자가 갤러리에서 선택한 이미지를(uri)
2. S3에 순서대로!! 여러장을 한꺼번에!! 업로드하고
3. 업로드한 이미지 링크(url)를 순서대로!! 한꺼번에!! 받아오기
플젝할 때 이미지 url을 DB에 저장하기로 했는데
순서가 중요하단 말임..??
순서대로 업로드하고, 그 링크를 순서대로 저장하기 위해 RxKotlin을 사용해 보았습니다..
S3와 Burket은 이미 생성되어있다고 가정한 상태에서 설명을 해보겠습니다.
1.S3 Access Key와 Secret access key를 github에 보이지 않도록 저장
아래 글 참고!!
https://min-wachya.tistory.com/220
2. app단위 build.gradle에 라이브러리 추가(S3와 RxKotlin)
// S3
implementation 'com.amazonaws:aws-android-sdk-mobile-client:2.13.5'
implementation 'com.amazonaws:aws-android-sdk-cognito:2.13.5'
implementation 'com.amazonaws:aws-android-sdk-s3:2.13.5'
//RxAndroid, RxJava, RxKotlin
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.17'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'
implementation 'com.uniquext.android:rxlifecycle:2.0.0'
3. Manifest에 권한 추가 + S3 Service 추가
<!--Glide, S3 사용 위한 권한-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application ...>
<activity
...
</activity>
<service android:name="com.amazonaws.mobileconnectors.s3.transferutility.TransferService" android:enabled="true"/>
</application>
4. MultiUploaderS3Client class 생성
이 클래스의 목적은 아래와 같다.
1. S3에 이미지를 순서대로 업로드
2. 업로드 성공한 이미지의 URL을 받아와 저장
나는 모든 이미지가 성공한 뒤 처리할 이벤트가 있었기 때문에
클래스의 매개변수로 viewModel을 넣었고, 그 viewModel의 LiveData 변수를 listImageUrl을 넣어 초기화함으로써
완료 시점에 다른 이벤트를 실행할 수 있도록 구현하였다.
순서가 중요하지 않은 경우에는 이 링크를 참고하여 구현하면 된다.
나는 순서가 굉장히!! 중요했기 때문에
순서를 지켜주기 위해 Uri와 File이 들어간 Map도 LinkedHashMap을 사용하였으며,
flatMapCompletable로 하면 Map 안의 아이템들 순서를 유지하지 않기 때문에 concatMapEager를 사용했다.
// 이거 대신에
Observable.fromIterable(fileToKeyUploads.entries)
.flatMapCompletable { entry ->
uploadSingle(~)
}
// 이거 사용
Observable.fromIterable(fileToKeyUploads.entries)
.concatMapEager {
uploadSingle(~~).toObservable()
}.toList()
하지만 concatMapEager를 사용한다 해서 순서가 꼭 지켜지는 건 아니었다!!!
이미지1, 이미지2, 이미지3 이런 순서로 uploadSingle함수가 실행되는 건 맞았지만
이미지 크기나 용량 등에 따라 업로드가 완료되는 타이밍이 각각 달랐기 때문에
count 변수를 index로 사용하여 이미지 업로드 완료 후 listImageUrl에 저장되는 순서도 지켜주었다!!
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.util.Log
import com.amazonaws.auth.BasicAWSCredentials
import com.amazonaws.mobileconnectors.s3.transferutility.TransferListener
import com.amazonaws.mobileconnectors.s3.transferutility.TransferNetworkLossHandler
import com.amazonaws.mobileconnectors.s3.transferutility.TransferState
import com.amazonaws.mobileconnectors.s3.transferutility.TransferUtility
import com.amazonaws.regions.Region
import com.amazonaws.regions.Regions
import com.amazonaws.services.s3.AmazonS3Client
import com.example.safetymanagement2022.common.TAG
import com.example.safetymanagement2022.ui.list.buildingcreate.BuildingCreateViewModel
import io.reactivex.Observable
import io.reactivex.Single
import java.io.File
import java.net.URL
// 이미지를 S3에 여러장 + 순서대로 업로드하는 클래스
class MultiUploaderS3Client(private val bucketName: String, context: Context, val vm: BuildingCreateViewModel) {
private var maxSize = 0 // 선택한 이미지의 최대 갯수
lateinit var listImageUrl: Array<String> // URL을 순서대로 저장해둘 배열
private var count = 0 // 순서대로 저장하기 위해 필요한 변수(index로 사용됨)
private val ai: ApplicationInfo = context.packageManager
.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
private val ak: String = ai.metaData["accessKey"].toString()
private val sak: String = ai.metaData["secretAccessKey"].toString()
private val wsCredentials: BasicAWSCredentials = BasicAWSCredentials(ak, sak)
private val s3Client: AmazonS3Client =
AmazonS3Client(wsCredentials, Region.getRegion(Regions.AP_NORTHEAST_2))
private val transferUtility: TransferUtility = TransferUtility.builder()
.s3Client(s3Client)
.context(context)
.build()
init {
TransferNetworkLossHandler.getInstance(context)
}
// 여러장을 한꺼번에 업로드
// fileToKeyUploads: <Uri를 String화한 것, File>로 된 Map
fun uploadMultiple(fileToKeyUploads: LinkedHashMap<String, File>): Single<MutableList<Unit>>? {
maxSize = fileToKeyUploads.size // 이미지 최대 갯수 초기화
listImageUrl = Array(maxSize) { "" } // url 배열 초기화
// 사진 하나씩 순서대로 업로드(uploadSingle)
return Observable.fromIterable(fileToKeyUploads.entries)
.concatMapEager {
uploadSingle(
transferUtility,
it.value, // File
it.key, // Uri to String
count++ // index
).toObservable()
}.toList()
}
// 사진 하나씩 업로드하고 url 받아와서 저장하기
private fun uploadSingle(
transferUtility: TransferUtility?,
aLocalFile: File?,
toRemoteKey: String?,
index: Int
): Single<Unit> {
return Single.create {
transferUtility?.upload(bucketName, toRemoteKey, aLocalFile)
?.setTransferListener(object : TransferListener {
override fun onStateChanged(id: Int, state: TransferState?) {
if (state == TransferState.COMPLETED) {
// 업로드 성공 시 URL 저장하기
val s3Url: URL = s3Client.getUrl(bucketName, toRemoteKey)
listImageUrl[index] = s3Url.toString()
// 모든 이미지 업로드 성공 시 viewModel의 변수를 listImageUrl로 초기화
if(listImageUrl.size == maxSize) vm.setArrS3Url(listImageUrl)
}
}
override fun onProgressChanged(id: Int, bytesCurrent: Long, bytesTotal: Long) {
val done = bytesCurrent / bytesTotal * 100.0
Log.d(TAG, "UPLOAD - - ID: $id, percent done = $done")
}
override fun onError(id: Int, ex: Exception?) {
Log.d(TAG, "UPLOAD ERROR - - ID: $id - - EX:" + ex.toString())
}
})
}
}
}
5. Fragment나 Activity에 아래 함수를 정의해놓고 S3에 업로드할 때 사용
private fun uploadImageToS3(map: LinkedHashMap<String, File>) {
val buildingId = requireArguments().getInt(KEY_BUILDING_ID)
MultiUploaderS3Client(
"burket이름/저장되길 원하는 폴더명/$buildingId",
requireContext(),
viewModel
).uploadMultiple(map)
?.subscribeOn(Schedulers.io())
?.observeOn(Schedulers.io())
?.subscribe()
}
끝입니다^_^
이 기능을 몇시간 내에 완성해내야 했기 때문에... 평소 써보고 싶었던 RxKotlin을 대충 다룬 거 같아서 아쉽다.
RxKotlin에 대한 공부를 더 해보고 이 기능을 다시 수정해봐야겟다
참고
https://stackoverflow.com/questions/46059515/amazon-s3-upload-multiple-files-android
'코딩 일기장 > Android(Kotlin)' 카테고리의 다른 글
[Android] Google Login API 사용해보기 (0) | 2023.05.08 |
---|---|
[Android/Kotlin]Kakao Address api 사용해보기 (0) | 2022.10.10 |
[Android/Kotlin] hide KEY/KEY 숨기기 (1) | 2022.09.11 |
[Android/Kotlin] Hilt 사용해보기 (2) | 2022.08.24 |
[Android/Kotlin] TextPicker/NumberPicker를 Custom해서 TextPicker 만들기/Dialog return value/다이얼로그에서 리턴값 받기 (0) | 2022.08.04 |