기본 콘텐츠로 건너뛰기

라벨이 android인 게시물 표시

[상추 키우기] 두 번째 상추...

 게으름으로 인하여 두 번째 상추 재배는 수확까지 끝난 다음에야 포스팅을 하게 되었습니다. 두 번째 상추 재배는 새로운 그로잉스펀지와 재배기에 심을 수 있는 12포트 중에서 4포트만 사용하여 상추를 재배했습니다. 그리고, TDS 측정기를 이용하여 양액의 농도 측정도 하면서 재배를 하였습니다. 수경 재배기를 살 때 포함되어 왔던 그로잉스펀지는 지난번 상추 재배로 인하여 모두 소비되어서 알리에서 새로운 스펀지를 주문했습니다. 주문하면서 실수로 동그란 스펀지가 아닌 네모 스펀지를 주문하는 바람에 포트에 넣었을 때 약간의 공간이 생깁니다만 크게 문제가 되지는 않습니다. 그런데 이번에 스펀지는 번들 스펀지에 비해서 밀도가 높다라고 해야할지 스펀지에 구멍이 적은 것 같습니다. 상추 뿌리가 스펀지를 뚫고 나오는데 지난번 보다 오래 걸린것 같습니다. 다음에 주문할 때에는 이런 부분을 좀 주의해야 할 것 같습니다. 그리고, 스펀지와 함께 TDS 측정기도 함께 구매해서 양액의 농도도 측정하였습니다. 상추의 경우 560 ~ 840 정도의 TDS 범위로 양액을 맞춰주라고 하는데, 수경 재배기에 번들된 양액을 12포트 분량으로 혼합하면 이 범위를 만족합니다. 지난번 재배에서는 이 값을 몰라서 싹을 틔우는 시기와 어린 시기에 양액 농도를 낮게 했었는데 그럴 필요는 없었던 것 같습니다. 약 5주 가량 길러서 수확하기 직전의 상태입니다. 12포트를 키울 떄 보다 빛을 잘 받아서 그런지 웃자람도 없고, 상치 잎의 크기도 지난번보다 상대적으로 컸습니다. 보통 상추를 키울때, 잎이 커지면 일부를 따 먹으면서 계속 키우는데 4포트만 키우게 되면 잎을 따 먹기에는 양이 너무 적어서 다 키워서 한번에 수확을 해야 했습니다. 그래서 다음 재배는 포트수를 조금 더 늘려서 시도해보겠습니다. 

[flutter] deep link

 개발하고 있는 앱에서 다른 사용자를 앱으로 초대하는 기능을 추가하게 되어 앱에 deep link 기능을 앱에 구현하게 되었습니다. 구현에 필요한 자료는 이미 더 잘 정리되어 있는 많은 자료들이 있으니 그것들을 참고하면 될 것 같아서 여기에서는 특별히 설명하지 않습니다. 다만 내 앱에 해당 기능을 넣기 위해서 자료를 찾고 구현하는 과정에서 헷갈렸던 것들과 구현중 문제가 있었던 부분에 대해서 프로젝트를 정리하는 차원에서 메모를 남기기로 해서 글을 쓰고 있습니다. 참고:  - https://itnext.io/deep-linking-in-flutter-boost-user-experience-in-apps-29dd25842757  - https://docs.flutter.dev/ui/navigation/deep-linking 용어 deep link는 보통 웹에서 인덱스 페이지가 아닌 상세 페이지에 대한 다이렉트 링크를 생성하는 것을 말합니다. 예를 들어서 어떤 웹페이지를 사용하기 위해서는 홈 화면에서 로그인을 하고 몇 단계를 거쳐서 특정 페이지로 들어가야하는데, 이 기능에 대한 지름길을 만드는걸 deep link로 이해하면 되겠습니다. 그리고 deep link 구현관련하여 모바일에서는 OS에 따라서 custom scheme(url scheme), universal link, app link 등의 용어가 튀어 나오는데, 표준 URI 형식을 이용하여 앱을 실행시키기 위한 방법을 말한다고 이해하면 간단하겠다. 기술에 대한 상세로 들어가면 약간의 차이가 있으나 큰 틀에서 차이는 없기 때문에 deep link를 구현하기 위해서 app을 실행(lunch)하기 위한 기술이라고 보면 됩니다. 그리고, 검색하다가 보면 dynamic link라는 용어가 나오는데, firebase에서 제공하던 서비스인데 현재는 서비스 종료되었으니 무시하면 됩니다. Navigator / Router flutter로 deep link를 구현할 때 현재 앱이 Navigator를 이용...

[flutter] textScaleFactor

노안이 있는 사람이나 시각 장애가 있는 사람들은 모바일 환경의 기본 글자 크기에 불편함을 느끼는 사용자들을 위해서 iOS와 안드로이드에서는 접근성 설정을 통해서 글자 크기를 크게 또는 작게 설정할 수 있도록 지원하고 있습니다. 그리고, 앱 개발시에도 이를 고려하여 개발하라고 가이드하고 있습니다. 그렇지만 앱 개발 단계에서 이런 부분이 몰라서, 귀찮아서 또는 디자인과 같은 현실적 한계로 인하여 가이드를 무시하고 개발되는 경우가 종종 발생하게 됩니다. 이렇게 접근성 설정을 무시하고 앱을 개발하면 앱 접근성 설정을 이용하는 사용자 환경에서 화면이 깨지거나, 사용상의 문제를 발생시키는 경우가 발생합니다. 또는 앱 심사 과정에서 거부되기도 합니다. 그런데 이런 문제가 인식된 시점에 디자인 변경이 어렵거나, 변경해야 하는 범위가 커서 접근성 설정을 무시하고 기본 글자크기로 나오도록 하여 문제를 회피하여 해결해야하는 방법을 알아보겠습니다. 아래 방법은 flutter에서 문자의 비율을 시스템에서 설정하는 값이 아닌 개발자가 기본 비율로 강제하도록 하여 접근성 설정이 무시되도록 하는 방법입니다. Widget build(BuildContext context) { // 사용자가 OS에서 제공하는 접근성 설정을 변경할 경우, 앱의 폰트 크기가 변경되는 것을 방지한다. // 폰트 크기가 크게 변경되면 다이얼로그의 레이아웃이 깨져서 다음 단계로 진행하지 못하는 경우가 발생함. final mediaQueryData = MediaQuery.of(context); final newMediaQueryData = mediaQueryData.copyWith(textScaleFactor: 1.0); return MediaQuery( data: newMediaQueryData, child: Container() ) } 내용이 잘못되었거나, 더 좋은 방법이 있으면 댓글 남겨주시기 바랍니다.

[flutter] 모바일 앱 재시작 또는 종료

모바일 앱을 개발하다 보면 앱을 재실행 시키거나 코드에 의한 종료를 해야하는 경우가 발생합니다. 예를 들어서 앱의 전반에 걸쳐서 영향을 끼치는 설정을 초기화하고 새로 로딩해야 하는 경우가 있습니다. 이런 경우에는 해당 설정값만 다시 로딩하는 방법도 있겠지만, 이 값이 여러곳에 영향을 끼친다던가 초기화 해야하는 값들이 많다던가 하면 머리가 아파지고, 앱을 재시작 시켜서 자연스럽게 설정값을 읽어들지게 하는게 자연스러울 수 있습니다. 그런데, iOS와 안드로이드 모바일 환경에서는 코드에 의해서 앱을 재실행 시키거나, 종료 시키는 것에 대해서는 권장하지 않으며, iOS의 경우에는 이런 기능이 들어가면 앱 스토어 심사 단계에서 거부될 수 있습니다. 그래도 좋은 사용자 경험을 위해서 앱의 종료나 재시작이 꼭 필요한 경우에는 구현을 하는게 좋습니다. 앱 재시작 설정 초기화 후 앱을 재로딩이 필요한 경우라면 앱이 자동으로 재시작 되는 것이 앱이 종료되는 것 보다는 더 좋은 사용자 경험을 제공합니다. 그런데 iOS에서는 앱을 재시작 시킬 수 있는 방법을 찾지 못해서 안드로이드에서 재실행 시키는 방법만 정리하도록 하겠습니다. flutter 에서 제공하는 기능만으로 앱을 재시작 시키는 방법은 아직 찾지 못했고, 메소드 채널을 이용하여 네이티브 코드로 구현하셔야 합니다. PackageManager packageManager = getPackageManager(); Intent intent = packageManager.getLaunchIntentForPackage(getPackageName()); ComponentName componentName = intent.getComponent(); Intent mainIntent = Intent.makeRestartActivityTask(componentName); startActivity(mainIntent); System.exit(0); 참고 : https://ideaj...

Handshake error in client (OS Error: CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:393))

현재 flutter로 개발 중인 프로젝트에서 REST API 통신을 하는데, 안드로이드 단말에서 CERTIFICATE_VERIFY_FAILED 오류가 발생하면서 서버와 통신이 이뤄지지 않는 문제가 발생하였습니다. 현재 상황은 apache 서버에 php를 이용하여 api를 만들었고, 운영서버에는 SSL 이 적용되어 있습니다. SSL 인증서는 공인 인증 기관으로 부터 발급된 인증서를 사용하고 있습니다. iOS에서는 문제없이 서버와 통신이 이뤄지지만 안드로이드에서는 오류가 발생합니다. CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate "CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate"로 검색을 하면 공인 기관에서 발급하지 않고 자체적으로 인증서를 발급받아서 사용하는 경우에 문제 해결 방법들이 검색됩니다. 앱 내부에서만 사용하는 서버인 경우에 자체 발급된 인증서를 많이 쓰는 모양입니다.  자체 인증서로 인한 문제를 해결하는 방법은 두가지 정도가 있는 듯 합니다.  1. 인증서 검증을 하지 않는 방법 Dio dio = new Dio(); (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (HttpClient client) { client.badCertificateCallback = (X509Certificate cert, String host, int port) = true; return client; }; 2. 인증서를 내장하는 방법 ByteData data = await rootBundle.load('assets/raw/certificate.pem'); SecurityContext context = SecurityContext.defaultConte...

zip4j 2.11.2 버전 호한성 이슈

 암호가 걸린 폴더 단위 압축 파일을 생성하는 기능이 필요하여, 안드로이드에서는 zip4j, iOS에서는 SSZipArchive 라이브러리를 사용하게 되었습니다.  api 레벨에서 zip 압축을 지원하기도 하는데, 폴더 단위 압축과 암호 설정이라는 조건을 더하면 구현도 복잡하고, 압축 파일에 대해서 잘 모르기 때문에 오픈소스의 힘을 빌리게 되었습니다. 그런데 오픈 소스 라이브러리들도 두가지 조건을 지원하는게 별로 없어서 어떤 면에서는 어쩔수 없이 두개 라이브러리를 선택했다고 보시면 됩니다. 물론 두개 라이브러리가 사용자도 많고, 프로젝트도 상대적으로 활성화되어 있는 상태입니다. NoSuchMethodError zip4j와 SSZipArchive를 이용하셔 개발은 순조롭게 진행이 되었습니다만, 테스트 과정에서 안드로이드 api 26 미만에서 아래와 같은 오류가 발생함이 확인되었습니다. ava.lang.NoSuchMethodError: No virtual method toPath()Ljava/nio/file/Path; in class Ljava/io/File; or its super classes (declaration of 'java.io.File' appears in /system/framework/core-libart.jar) at net.lingala.zip4j.util.FileUtils.getRelativeFileName(FileUtils.java:225) at net.lingala.zip4j.tasks.AbstractAddFileToZipTask.cloneAndAdjustZipParameters(AbstractAddFileToZipTask.java:218) at net.lingala.zip4j.tasks.AbstractAddFileToZipTask.addFilesToZip(AbstractAddFileToZipTask.java:71) at net.lingala.zip4j.tasks.AddFolderToZi...

Android Emulator closed because internal error:

  최근에 안드로이드 버전에 개발에 충실(?)했더니 iOS 개발 환경에 대한 관리를 소홀히 했었는데, iOS 버전 업데이트를 위해서 작업을 시작했더니, XCode, macOS 업데이트 등을 해야만 하는 상황이 다시 찾아왔습니다. XCode, macOS 업데이트를 하고 여러가지 추가적인 작업들을 어찌어찌  해줘서 iOS 개발 환경을 안정화시켰더니, 안드로이드 개발환경에서 애뮬레이터를 종료하면 아래와 같은 오류 리포트가 계속 출력되네요.. Android Emulator closed because internal error: emuglConfig_init: blacklisted=0 has_guest_renderer=1 아마 macOS 업데이트하면서 관련 드라이버들이 변경되면서 발생하는 문제로 보입니다. 해결은 Android Studio -> Preference -> Appearance & Behavior -> System Settings -> Android SDK 에서 SDK Tools 중 Android SDK Platform-Tools 최신 버전으로 업데이트해서 해결했습니다. 오류 내용을 구글에서 검색을 하면 그래픽 드라이버 업데이트, AVD를 코멘드로 실행하면서 -gpu host 옵션 주는 것 등이 있었으나 저에게는 해당사항이 없었습니다. 다음에 OS 업데이트 하면 또 발생할 수 있는 문제라서 그때 어렴풋이 기억이 나도록 흔적을 남깁니다.

Java, Android, Gradle.. 익숙해지지 않는 개발 환경...

 나는 여러 해 동안 iOS/안드로이드 개발을 해오고 있다. iOS/안드로이드 개발하기 이전에는 MFC 기반의 개발을 다년간 했었고, 일로 인해서 VB, ASP, JAVA, JSP 등도 약간씩 하기도 했었습니다. MFC 개발 환경에 익숙했어서 인지, 늙어서 그런지, 게을러서 그런지 안드로이드 개발에서 아직도 적응이 되지 않는 부분이 Gradle 기반으로 하는 빌드 환경 구성 부분은 아직도 익숙해지지 못했습니다. 게을러서이겠지요. 예전에 MFC 기반으로 개발하던 시절에 비해서 안드로이드 개발 환경은 변화가 비교적 자주 발생하고, SDK 버전 변경시, Android Studio 업데이트 시 어쩔 수 없이 뭔가를 해줘야하는 상황이 발생하는데 그때 그때 찾아보고 대응하는 식으로 하다보니 문제를 키우는게 아닌가 싶기도 합니다. 그리고, 현업에서 개발환경에 변화를 줬다가 기대치 않은 문제가 발생하기도 하고, 급히 처리해야 하는 고객 요구 사항을 제때 대응하지 못하는 문제가 발생할 수 있어서, 반드시 업데이트가 필요한 상황이 아니면 개발 환경은 건드리지 않는 것을 철칙으로 삼아오다보니 새로운 개발환경 적응에 늦게 따라갈 수 밖에 없기도 했습니다. 최근에 flutter 기반으로 개발을 하고 있다보니 내 입장에선 좀 더 복잡해진것 같은데, 서론이 너무 길어진것 같으니 본론으로 들어가도록 하겠습니다. 얼마전부터 개발을 하면서 run 명령을 내리면 에러가 한번 발생하고 다시 실행하면 실행이되는 문제가 발생하기 시작했습니다. 예를 들어서 Log.d(TAG, ""); 와 같은 오류가 없는 라인을 추가 했음에도 오류가 한번 발생하고 다시 실행하는 정상 실행되는 이해할 수 없는 문제였습니다. 처음 문제가 발생했던 시점에는 처리해야할 급한 일들이 산재해 있고, flutter는 hot reload라는 기능을 통해서 빌드 없이(?) 바로 수정 사항이 반영되기 때문에 일을 하는데 크게 불편하지 않은 상황이어서 무시하고 진행을 했습니다. 그런데 최근에 flutter 쪽 코드가 ...

FlutterJNI.java line 116 io.flutter.embedding.engine.FlutterJNI.loadLibrary

 회사에서 운영중인 앱을 flutter로 개발 환경을 변경하고, 스토어에 배포를 한 이후에 몰려오는 문제로 폭풍같은 기간을 보내고 있는데, 건수는 많지 않으나 유독 앱이 실행하지 않는다라는 고객 불만이 많았습니다. crashlytics에 아래와 같은 비정상 종료 로그가 그 불만의 원인으로 추정되었습니다. Fatal Exception: java.lang.RuntimeException Unable to start activity ComponentInfo{xxx.xxxxxxxxx.xxxxxxxxxxxx/xxx.xxxxxxxxx.xxxxxxxxxxxx.MainActivity}: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/base.apk", zip file "/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/split_config.xxxhdpi.apk"],nativeLibraryDirectories=[/data/app/xxx.xxxxxxxxx.xxxxxxxxxxxx-K_hBqtMu3gBO-zaDGjmAZA==/lib/arm64, /system/lib64, /hw_product/lib64, /system/product/lib64]]] couldn't find "libflutter.so" 오류는 간단하게도 libflutter.so 파일을 찾을 수 없다라는 것입니다. 오류 내용 만으로는 앱이 동작하지 않는 것이 매우 당연한 것입니다. 그런데 왜 libflutter.so가 빠져 있고, 일부 사용자만 문제가 발생하는 것일...