서버를 보유 하지(Serverless) 않고 서버가 있는 것처럼 업무 구현을 하고 싶습니다. 개발을 하면서 데이터 베이스와 스토리지를 firebase을 활용하고 있다면 cloud function 도 배워서 준비를 해야 할 것 같습니다. 지금 개발하고 있는 앱이 payapp와 API 연동을 해야 하지만, 가난한(?) 개발자는 서버가 없습니다
대안으로 생각해 볼 수 있는 것을 찾아보다가 알게 된 cloud function의 구현에 대한 이야기를 해 보겠습니다.
* google cloud function : python, node.js, java 등등이 지원이 된다고 합니다. ** firebase cloud function : node.js 만 (2022.11.12 현재로는) 지원이 되고 있습니다
node.js을 잘 알고 있다면 firebase을 활용할 수 도 있겠지만, 아직은 python을 더 잘 알고 있기 때문에 google cloud function을 구현해 보기로 했습니다.
이 그림이 나의 목표 입니다. 서버가 없기 때문에 payapp의 API을 호출했을 때, 상대방 서버에서 feedback url로 보내주는 결과를 수신해서 database에 저장하는 것이 목표입니다.
이걸 준비 하는 과정은 가이드에 따라서 잘 따라가기를 하면 되기는 했습니다. 단지, 이해가 어려웠던 부분이 있다면 1. cloud client 설치 2. project 설정 ( 혹시 console.google 에 project 가 여러 개 있을 때 project을 선택해야 하는 문제) 3. 해당 project의 결제 계정 연결 4. function 연동 설정
이런 정도라고 볼 수 있습니다. 한번 해 보고 나서 정리를 하는 것이라, 쉽게 말하고 있기는 하지만, 이것 설정을 준비하는 동안만 3~4일 걸렸고, 어떻게 할까에 대한 고민을 여러 가지로 했습니다. 아무튼 이제 할 수 있습니다. 따라 하는 설명은 다음에 하도록 하겠습니다.
class LocalWebserver(context: Context, port: Int) : NanoHTTPD(port) {
var context: Context? = context val MIME_JAVASCRIPT = "text/javascript" val MIME_CSS = "text/css" val MIME_JPEG = "image/jpeg" val MIME_PNG = "image/png" val MIME_SVG = "image/svg+xml" val MIME_JSON = "application/json" val MIME_GIF = "image/gif" val MIME_BMP = "image/bmp" var mimeType = MIME_HTML val folderName = "portfolio" //name of the folder holding the asset of the page you wanna load
private fun getStoragePath(): String? { var path: String? = null var space: Long = 0 val files: Array<File> = ContextCompat.getExternalFilesDirs(context!!, null) // go through the options to choose one with more available storage capacity for (f in files) { val stat = StatFs(f.path) val blockSize = stat.blockSizeLong val totalBlocks = stat.blockCountLong
// check if storage capacity is more than the previous one if (totalBlocks * blockSize > space) { space = totalBlocks * blockSize path = f.path } } return path }
private fun getLocalIpAddress(): String { try { val en = NetworkInterface.getNetworkInterfaces() while (en.hasMoreElements()) { val intf = en.nextElement() val enumIpAddr = intf.inetAddresses while (enumIpAddr.hasMoreElements()) { val inetAddress = enumIpAddr.nextElement() if (!inetAddress.isLoopbackAddress && inetAddress is Inet4Address) { return inetAddress.getHostAddress().toString() } } } } catch (ex: SocketException) { ex.printStackTrace() } return "" } }
코드는 참조한 링크의 내용을 그대로 구현 했습니다. 잘 모르는 부분도 있고 해서요. 코드를 보면 local IP을 확인하고, 해당 아이피를 기준으로 웹서버를 구동하는 그런 정도입니다. 추가적으로 해야 할 부분은 이제 화면에서 보여줄 html 코드를 구현해 보는 것인데,
아직은 그럴 생각이 없기 때문에 그냥 이렇게 구현 하는 정도까지만 포스팅을 해 두겠습니다. 이제 실행을 해 볼까요?
웹서버 구동
var localWebserver = LocalWebserver(this@MainActivity, 8035) localWebserver.start()
이런 정도의 코드 구현으로 구현이 됩니다. 그러면 위 구현된 코드에서 getStoreagePath()에서 구해온 경로에 index.html을 등을 넣어 주면 호출이 되는 것을 알 수 있었습니다.
구동된 로그 샘플
크롬 등으로 현재 테스트 중인 앱의 서버에 접속을 해 보고 그 실행 로그가 출력되는 것을 확인하였습니다. 지금은 내가 사용하는 스마트폰에 wifi debugging을 실행하고 앱을 빌드해서 실행해 본 것입니다.
@Composable fun CustomCircularProgressBar(visible : Boolean){ Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center ) { AnimatedVisibility( visible = visible, enter = fadeIn( // Overwrites the initial value of alpha to 0.4f for fade in, 0 by default initialAlpha = 0.4f ), exit = fadeOut( // Overwrites the default animation with tween animationSpec = tween(durationMillis = 250) ) ) { LinearProgressIndicator( modifier = Modifier .fillMaxWidth() .height(3.dp), backgroundColor = Color.LightGray, color = softBlue ) } }
}
위 코드 구현과 같이 구현이 됩니다. 이렇게 구현한 것은 MainActivity에서 boolean 형으로 파라미터를 받아와서 progressbar 가 보이고 어떤 action 이 마무리되면 boolean의 값이 false 가 되면서 progressbar을 사라지는 기능을 구현하기 위해서 AnimatedVisibility 을 사용 했습니다. 이렇게 구현 하면 progressbar 을 필요에 따라서 보이고, 사라지고를 선택할 수 있게 됩니다.
앞서 링크에서 설명을 보는 것처럼 Circular, Linear 형 선택적으로 사용할 수 있습니다. 일반적으로 Circular을 사용하는 것이기는 하나, 이번에는 Linear 형으로 구현을 해 보았습니다.
@OptIn(ExperimentalPermissionsApi::class) @Composable private fun FeatureThatRequiresCameraPermission( doResult:(ty:Boolean) -> Unit ) {
// Camera permission state val cameraPermissionState = rememberPermissionState( Manifest.permission.CAMERA )
when (cameraPermissionState.status) { // If the camera permission is granted, then show screen with the feature enabled PermissionStatus.Granted -> { doResult(true) } is PermissionStatus.Denied -> { Column( modifier = Modifier.padding(3.dp), horizontalAlignment = Alignment.End ) { val textToShow = if ((cameraPermissionState.status as PermissionStatus.Denied).shouldShowRationale) { // If the user has denied the permission but the rationale can be shown, // then gently explain why the app requires this permission stringResource(id = R.string.msgGetPermissonCamera) } else { // If it's the first time the user lands on this feature, or the user // doesn't want to be asked again for this permission, explain that the // permission is required stringResource(id = R.string.msgGetPermissonCamera) } IconButton(onClick = {
#스하리1000명프로젝트, Kadang-kadang susah nak bercakap dengan pekerja asing kan? Saya membuat aplikasi mudah yang membantu! Anda menulis dalam bahasa anda, dan orang lain melihatnya dalam bahasa mereka. Ia auto-terjemah berdasarkan tetapan. Sangat berguna untuk sembang mudah. Lihatlah apabila anda mendapat peluang! https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416
앱을 구현하는 동안 이런 것도 생각을 해 볼 수 있었습니다. 지문인증은 어떻게 구현하는 건가?
그래서 구글에게 물어보았습니다. 어떻게 하는 거냐고... 늘 항상 답을 보여 주기는 하나 긴가 민가 하는 생각이 들 무렵 게시글 하나를 찾았고 그것을 따라 해 보기로 했습니다.
gradle 설정
// bio implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
module 수준의 gradle 파일에 추가된 것은 위 한 줄입니다.
Hardware 검증
이번에는 지문 인증을 사용할 수 있는 것인지 확인하는 처리를 해 봅니다. 아래 코드의 함수를 호출하게 되면,
사용이 가능한 상태 (BIOMETRIC_SUCCESS),
지문인식 센서가 없는 경우(BIOMETRIC_ERROR_NO_HARDWARE),
지문인식 센서가 이미 동작하고 있어서 준비가 되지 않은 경우(BIOMETRIC_ERROR_HW_UNAVAILABLE)
지문이 등록되지 않은 경우(BIOMETRIC_ERROR_NONE_ENROLLED)
이런 경우의 오류(또는 상태)를 만나게 됩니다. 지문 등록이 되지 않은 경우는 등록을 하도록 안내하는 페이지로 이동하도록 구성하고, 다른 경우들은 그에 맞게 알림을 주거나 인증 후 동작을 구현하게 됩니다. 여기서는 사용 가능한 상태만 저장하는 것으로 마무리를 합니다.
fun checkDeviceHasBiometric() { val biometricManager = BiometricManager.from(this) when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL)) { BiometricManager.BIOMETRIC_SUCCESS -> { Log.e("MY_APP_TAG", "App can authenticate using biometrics.") info = getString(R.string.biometric_success) // "App can authenticate using biometrics." productsViewModel.isBioAuth.value = true
} BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> { Log.e("MY_APP_TAG", "No biometric features available on this device.") info = getString(R.string.biometric_error_no_hardware) // "No biometric features available on this device." productsViewModel.isBioAuth.value = false
} BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> { Log.e("MY_APP_TAG", "Biometric features are currently unavailable.") info = getString(R.string.biometric_error_hw_unavailable) // "Biometric features are currently unavailable." productsViewModel.isBioAuth.value = false
} BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> { // Prompts the user to create credentials that your app accepts. val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply { putExtra( Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL ) } productsViewModel.isBioEnabled.value = false
위에 나와 있는 글을 읽어 보면 firebase에서 오류 감지를 위해서 사용하고 있는 API에 오류가 있는 것처럼 보입니다.
crashlytics는 firebase에서 제공하는 API 중에서 앱의 오류를 감지합니다. playstore 에도 릴리즈 된 앱의 경우는 그 내용이 감지되어 알려 주기는 하지만, firebase의 경우는 조금 더 상세하게 알게 되는 것 같아서 사용하는 중입니다.
위 링크 글에서 보는 것처럼 최종 버전인 것 같은 18.3.0은 사용을 보류하고 18.2.3으로 당분간은 유지를 해지 않나 하는 생각을 하면서 이 글을 정리해 봅니다.
관련이미지
--- 아래 내용은 링크된 글의 번역한 내용입니다. ---
문제 설명
Android 앱 내에서 충돌이 발생하는 경우(예: 앱 내에서 버튼을 누를 때 NullPointerException 발생) 앱을 다시 시작할 때 앱이 로드되지 않고 com.google.android.datatransport.runtime 누락으로 인해 충돌합니다. 이 버전에서 새로 추가된 ForcedSender 클래스입니다.
이것은 crashlytics 종속성만 사용하여 18.3.0으로 업데이트할 때 발생합니다. 구현 'com.google.firebase:firebase-crashlytics:18.3.0' 저는 BOM 종속성을 사용하지 않습니다(따라서 BOM을 사용할 때 이 종속성이 해결되었는지 모르지만 위의 사용법에서는 확실히 누락되었습니다.)
그 사이에 18.2.13이 안정적이어서 되돌렸습니다.
재현 단계:
무슨 일이에요? 문제가 발생하도록 하려면 어떻게 해야 합니까? Crashlytics 18.3.0으로 업데이트하면 앱이 충돌한 다음 다시 시작하려고 합니다.
#billcorea #운동동아리관리앱 🏸 Schneedle, aplikasi mesti ada untuk kelab badminton! 👉 Main Perlawanan – Rekod Markah & Cari Lawan 🎉 Sesuai untuk mana-mana sahaja, bersendirian, bersama rakan-rakan atau dalam kelab! 🤝 Jika anda suka badminton, pasti mencubanya
Pergi ke aplikasi 👉 https://play.google.com/store/apps/details?id=com.billcorea.matchplay