[{"content":"","date":"10 ธันวาคม 2025","externalUrl":null,"permalink":"/categories/blog/","section":"Categories","summary":"","title":"Blog","type":"categories"},{"content":" Android TVM Demo: Running a Full Inference Pipeline with TVM + Chaquopy # Before wiring everything together, there are a few important requirements and project-structure rules that you must follow when running a real model inside an Android TVM demo.\nThis section explains:\nWhere to put your compiled TVM models How TVMRunner loads asset files What versions of Apache TVM work Gradle / NDK requirements Chaquopy build quirks How to structure your inference pipeline into Android ↔ Python stages 0. Code for compiling MXNet Model to TVM Models. (Recommend using NDK29 as it supports 16 KB Page Size which require for Android 16) # import os # ====================================================== # 🔧 CONFIG # ====================================================== # TODO Set to True if compiling for local machine and set the target accordingly local = False ndk_path = os.environ.get(\u0026#34;ANDROID_NDK\u0026#34;) if ndk_path is None and not local: raise EnvironmentError(\u0026#34;Please set ANDROID_NDK environment variable for cross-compilation.\u0026#34;) toolchain = f\u0026#34;{ndk_path}/toolchains/llvm/prebuilt/linux-x86_64/bin\u0026#34; cross_cc = f\u0026#34;{toolchain}/aarch64-linux-android21-clang\u0026#34; import mxnet as mx import tvm from tvm import relay from tvm.contrib import cc # ====================================================== # 🧠 1. Load your MXNet model # ====================================================== # Replace these with your own model filenames (without extensions) # TODO ./path/to/file/my-model prefix = \u0026#34;./ecg12_v2/ECG12Net_v2 (MI-1)\u0026#34; # TODO epoch 0000 epoch = 0 # or whatever number matches your params filename output_prefix = \u0026#34;./a_very_new_ecg_AMI/\u0026#34; os.makedirs(output_prefix, exist_ok=True) # This loads: my_model-symbol.json and my_model-0000.params sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch) # ====================================================== # 🧩 2. Convert to Relay IR # ====================================================== # TODO change shape as needed shape_dict = {\u0026#34;data\u0026#34;: (11,1,12,1024)} # change if your model expects different input mod, params = relay.frontend.from_mxnet( sym, shape_dict, arg_params=arg_params, aux_params=aux_params ) # ====================================================== # ⚙️ 3. Build with TVM (CPU target example) # ====================================================== if local: target = \u0026#34;llvm\u0026#34; # or \u0026#34;llvm -mtriple=aarch64-linux-android\u0026#34; for Android else: target = \u0026#34;llvm -mtriple=aarch64-linux-android\u0026#34; with tvm.transform.PassContext(opt_level=3): lib = relay.build(mod, target=target, params=params) # Optional cross-compile for Android if local: compile = None else: compile = cc.cross_compiler( cross_cc, options=[ \u0026#34;-lm\u0026#34;, # \u0026#34;-Wl,-z,max-page-size=0x4000\u0026#34; ], ) lib.export_library(os.path.join(output_prefix, \u0026#34;deploy_lib_cpu.so\u0026#34;), fcompile=compile) # ====================================================== # 📦 4. Export artifacts # ====================================================== with open(os.path.join(output_prefix, \u0026#34;deploy_graph.json\u0026#34;), \u0026#34;w\u0026#34;) as f: f.write(lib.get_graph_json()) with open(os.path.join(output_prefix, \u0026#34;deploy_param.params\u0026#34;), \u0026#34;wb\u0026#34;) as f: f.write(relay.save_param_dict(lib.get_params())) print(\u0026#34;✅ Export done: deploy_lib_cpu.so, deploy_graph.json, deploy_param.params\u0026#34;) 1. Project Requirements # ✅ 1.1 Pre-built TVM models # This demo assumes you already compiled models using TVM (Relay → Graph Executor → .so + JSON + params). Place all compiled runtime artifacts inside:\nNote that this guide only works on Android Physical Device (arm64-v8a), if you want to try it on emulator Android x86 on Windows, you need to rebuild the model to support it.\napp/src/main/assets/ Example:\nassets/ └── standard/ami └── deploy_graph.json └── deploy_param.params └── deploy_lib_cpu.so Your TVMRunner should point to these exact filenames:\nval jsonPath = \u0026#34;deploy_graph.json\u0026#34; val paramsPath = \u0026#34;deploy_param.params\u0026#34; val libPath = \u0026#34;deploy_lib_cpu.so\u0026#34; Assets are copied into the APK automatically.\n2. Apache TVM Version Compatibility # Tested and working versions:\nTVM 0.11.0 Older versions use slightly different graph executor APIs and may require patching. Newer builds (compared to upstream main) still work as long as the JNI runtime symbols match.\n3. Android Project Folder Layout # To ensure Gradle can find runtime sources, JNI paths, and tvm4j JAR files:\ntvm/ └── apps/ └── android_deploy/ ← your Android Studio project should be placed here Why? Some Gradle tasks depend on relative paths such as:\n../../../jvm/core/target/ ../../../jvm/native/src/main/native/ If you move the Android folder elsewhere, everything breaks.\n4. NDK Path Requirement (Very Important) # In your build.gradle.kts, you hard-point to your local NDK path:\nval ndkBuild = \u0026#34;C:\\\\Users\\\\Jojo\\\\AppData\\\\Local\\\\Android\\\\Sdk\\\\ndk\\\\29.0.13846066\\\\build\\\\ndk-build.cmd\u0026#34; or on linux with the ndk downloaded it will be at YOUR_PATH/android-ndk-r29/build/ndk-build.cmd or ndk-build\n✔ Make sure the version number matches the NDK version installed in: Android Studio → Settings → SDK Manager → Android SDK → SDK Tools → NDK\nIf you upgrade the NDK, this path must be updated.\n5. make/config.mk # Application default has CPU and GPU (OpenCL) versions TVM runtime flavor and follow below instruction to setup. In app/src/main/jni/make you will find JNI Makefile config config.mk and copy it to app/src/main/jni and modify it.\ncd apps/android_deploy/app/src/main/jni cp make/config.mk . Here\u0026rsquo;s a piece of example for config.mk.\nAPP_ABI = arm64-v8a APP_PLATFORM = android-17 USE_OPENCL = 0 # This build disable GPU, to use GPU, you need to build some library your self which is not covered in this topic. 6. Chaquopy Build Quirks # To run the inference on Android, we need to run the preprocessing and postprocessing which is in Python, so we will use the library call Chaquopy\nChaquopy is powerful, but sometimes Android Studio behaves… uniquely.\nCommon issue:\nCannot Run app on device, sdk not found. This happens even if the SDK is installed.\n✔ Fixes: # Click Sync Project with Gradle Files Run Build → Make Project If still broken → close Android Studio → reopen After reopening, Chaquopy usually resolves and the build succeeds This is a known Chaquopy behavior because it performs its own internal Python environment bootstrap.\n7. Designing a Real Inference Pipeline # Instead of calling a single big function, it’s cleaner and more maintainable to split the workflow across Android and Python (Chaquopy).\nseparate into\nAndroid Chaquopy (Python) Android Chaquopy (Python) Android Read JSON Preprocess Data Run TVM Inference Postprocess Data Show Result to User Input: (12, 5000) (11, 1, 12, 1024) Output: (11, 3) AMI classification (e.g., STEMI, NSTEMI, PSTEMI) Final UI Display Also you can see that some parts of the file which in python, there will be some change, including the removal of all mxnet traces, because we choose to run the model on Android\n8. Summary # Android → Read raw data Chaquopy → Preprocess Android → Run TVM Chaquopy → Postprocess Android → Display result This separation makes the demo reliable, maintainable, and easy to verify.\n","date":"10 ธันวาคม 2025","externalUrl":null,"permalink":"/posts/tvm-deploy/","section":"Posts","summary":"Despite the old models, some still have requirements to use this model","title":"Deploying Models to Android (Arm64) [PART-2]","type":"posts"},{"content":"","date":"10 ธันวาคม 2025","externalUrl":null,"permalink":"/tags/models/","section":"Tags","summary":"","title":"Models","type":"tags"},{"content":"","date":"10 ธันวาคม 2025","externalUrl":null,"permalink":"/tags/mxnet/","section":"Tags","summary":"","title":"Mxnet","type":"tags"},{"content":"","date":"10 ธันวาคม 2025","externalUrl":null,"permalink":"/categories/post/","section":"Categories","summary":"","title":"Post","type":"categories"},{"content":"","date":"10 ธันวาคม 2025","externalUrl":null,"permalink":"/series/tvm/","section":"Series","summary":"","title":"Tvm","type":"series"},{"content":"","date":"10 ธันวาคม 2025","externalUrl":null,"permalink":"/tags/tvm/","section":"Tags","summary":"","title":"Tvm","type":"tags"},{"content":" Ubuntu 22.04 → Relay → Android Runtime # Apache TVM v0.15.0 is the last release that supports Relay, making it the ideal version for deploying MXNet/PyTorch models on Android. This guide walks through building TVM on Ubuntu, cross-compiling the Android runtime, exporting Relay modules, and verifying everything on device.\nThis guide is tested on Apache TVM v0.11.0\n🎯 Goal # Build TVM v0.11 host tools for Relay model conversion Export models into deploy_lib.so, deploy_graph.json, deploy_param.params Test deployment using TVM’s Android demo app or RPC You will also need conda installation on your devices.\nPrerequisites # Conda Linux Android Studio Android NDK [LINK] , Recommended r29 (Supports Android 16KB Requirement Out of the box) TVM Binaries (Below) 1. Host Build (Linux) # 1.1. Download TVM v0.11.0 # Suggested downloading from here instead of cloning from github, as the repository have submodules which is very easy to miss.\nwget https://dlcdn.apache.org/tvm/tvm-v0.11.0/apache-tvm-src-v0.11.0.tar.gz tar -xzf apache-tvm-src-v0.11.0.tar.gz mv apache-tvm-src-v0.11.0 tvm \u0026amp;\u0026amp; cd tvm v0.15 is the last version supporting Relay (frontend for MXNet) Later versions use Relax which drops MXNet support.\n(Tested on v0.11)\n1.2. Environment Setup # conda create -n tvm-build-venv python=3.7 \u0026amp;\u0026amp; conda activate tvm-build-venv conda install -c conda-forge \\ llvmdev=15 clang=15 clangxx=15 cmake ninja make pip install \u0026#39;numpy\u0026lt;1.20\u0026#39; decorator scipy==1.6 psutil attrs mxnet==1.5.0 sudo apt install build-essential openjdk-17-jdk maven 1.3. Building TVM on Machine Host Build # mkdir build \u0026amp;\u0026amp; cp cmake/config.cmake build \u0026amp;\u0026amp; cd build Configure config.cmake for CPU-only build with Relay + GraphExecutor:\necho \u0026#34;set(CMAKE_BUILD_TYPE RelWithDebInfo)\u0026#34; \u0026gt;\u0026gt; config.cmake # reasonable debug info echo \u0026#34;set(USE_LLVM \\\u0026#34;llvm-config --ignore-libllvm --link-static\\\u0026#34;)\u0026#34; \u0026gt;\u0026gt; config.cmake # IR→LLVM echo \u0026#34;set(HIDE_PRIVATE_SYMBOLS ON)\u0026#34; \u0026gt;\u0026gt; config.cmake # smaller .so echo \u0026#34;set(USE_GRAPH_EXECUTOR ON)\u0026#34; \u0026gt;\u0026gt; config.cmake # legacy executor # Disable GPU backends (enable later if needed) echo \u0026#34;set(USE_CUDA OFF)\u0026#34; \u0026gt;\u0026gt; config.cmake echo \u0026#34;set(USE_METAL OFF)\u0026#34; \u0026gt;\u0026gt; config.cmake echo \u0026#34;set(USE_VULKAN OFF)\u0026#34; \u0026gt;\u0026gt; config.cmake echo \u0026#34;set(USE_OPENCL OFF)\u0026#34; \u0026gt;\u0026gt; config.cmake Then Build for host:\ncmake .. make -j4 The number is a total cores range from 1\u0026hellip;MAX_CORE\n1.4. Python Binding \u0026amp; Environment Variable # Append to ~/.bashrc:\nexport TVM_HOME=~/tvm export PYTHONPATH=$TVM_HOME/python:${PYTHONPATH} source ~/.bashrc Then build the JVM package:\ncd ~/tvm \u0026amp;\u0026amp; make jvmpkg \u0026amp;\u0026amp; make jvminstall 2. Verify if it is working (OR skip to PART-2 for Android ) # Noted that we will use the example app we downloaded from the apache-tvm earlier, we will find the apps/android_deploy\n2.1. Android App Setup (Gradle) # Create gradle/wrapper/gradle-wrapper.properties\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-7.5-bin.zip Use Gradle Wrapper + Amazon Corretto 17 as Gradle JDK if have any compatibility issues\nDownload NDK and ensure $ANDROID_NDK is visible in $PATH.\n2.2. Deploying Model in android_deploy app # After converting the model to Relay (TVM), the model file must be placed in the assets/ folder in github . Then need to fix some gradle and compatibility . Also Need to setup the correct ndk-build version in the gradle files. The repository already have the download-models.gradle which will be downloaded on first launch. If you need to use the custom model, the download-models.gradle import must be disabled in the build.gradle file.\nNote that whether you use the android_deploy from .zip or from the repository, it is missing a graph_executor_factory.cc in the jni/tvm_runtime.h, please add the line in.\nThen running the task build from gradle will compile and build the correct library for TVM.\nCommon Pitfalls \u0026amp; Fixes # Issue Cause Fix arg2.ndim is expected to equal 4 MXNet model shape mismatch Possible Wrong Runtime, try rebuilding TVM and rebuilding models GraphExecutorFactory not found Missinggraph_executor_factory.ccin tvm_runtime.h Add graph_executor_factory.cc under jni/tvm_runtime.h in Android Project undefined reference to 'sin' Missing math lib link Add-lmtofcompileflags ","date":"5 ธันวาคม 2025","externalUrl":null,"permalink":"/posts/tvm-build/","section":"Posts","summary":"Despite the old models, some still have requirements to use this model","title":"Building MxNet with Apache TVM v0.11 for Android (Arm64) [PART-1]","type":"posts"},{"content":" Screenshot security in Android is more complicated than it looks. # Most developers begin with the obvious solution — FLAG_SECURE — and quickly discover:\nIt hurts UX It prevents debugging and QA It blocks Recents previews Support cannot reproduce or visualize issues Users hate when they can’t screenshot their own data This is why modern apps use a hybrid approach:\nScreenshot detection Recents-screen protection Per-screen security (Jetpack Compose) FLAG_SECURE only where truly necessary This post covers everything from Android 10 → Android 14+.\n1. Why FLAG_SECURE Works — and Why It Hurts UX \u0026amp; Debugging # Android provides a simple, powerful way to block screenshots:\nwindow.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE ) When enabled, Android blocks:\nScreenshots Screen recordings Casting / mirroring Recents screen previews It’s instant and OS-level.\nBut the downside is huge:\nNo screenshots for:\nQA bug reports UX/UI review Developer debugging Customer support Users sharing receipts, queue numbers, or results So instead of blocking everything globally, a better strategy is detection + selective blocking.\n2. Screenshot Detection + Notify # Sometimes you don\u0026rsquo;t want to block screenshots—you just want to:\nWarn the user Mask sensitive UI Log the event Trigger additional security steps Android provides two ways to detect screenshots.\n2.1 Android 14+ — Official Screenshot Detection API # class MainActivity : ComponentActivity() { val TAG = this.javaClass.name val screenCaptureCallback = if (Build.VERSION.SDK_INT \u0026gt;= Build.VERSION_CODES.TIRAMISU) { Activity.ScreenCaptureCallback { Toast.makeText(this, \u0026#34;Screen captured\u0026#34;, Toast.LENGTH_SHORT).show() } } else null override fun onStart() { super.onStart() if (Build.VERSION.SDK_INT \u0026gt;= Build.VERSION_CODES.TIRAMISU) { registerScreenCaptureCallback( mainExecutor, screenCaptureCallback as Activity.ScreenCaptureCallback ) } } override fun onPause() { super.onPause() if (Build.VERSION.SDK_INT \u0026gt;= Build.VERSION_CODES.TIRAMISU) { unregisterScreenCaptureCallback(screenCaptureCallback as Activity.ScreenCaptureCallback) } } } Add to AndroidManifest.xml:\n\u0026lt;uses-permission android:name=\u0026#34;android.permission.DETECT_SCREEN_CAPTURE\u0026#34; /\u0026gt; 2.2 Android 10–13 — MediaStore Observer Detection # class ScreenshotDetector(private val context: Context) { private val observer = object : ContentObserver(Handler(Looper.getMainLooper())) { override fun onChange(selfChange: Boolean, uri: Uri?) { Toast.makeText(context, \u0026#34;Screenshot detected\u0026#34;, Toast.LENGTH_SHORT).show() } } fun start() { context.contentResolver.registerContentObserver( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, observer ) } fun stop() { context.contentResolver.unregisterContentObserver(observer) } } Use in an Activity:\noverride fun onResume() { super.onResume() scr.start() } override fun onPause() { super.onPause() scr.stop() } Minimum version: Android 10 (API 29)\n3. Blocking Recents-Screen Preview # 3.1 Android 13+ — Official API # if (Build.VERSION.SDK_INT \u0026gt;= 33) { setRecentsScreenshotEnabled(false) } What this does:\nHides your activity’s thumbnail in Recents What it does NOT do:\nDoes not block normal screenshots Does not stop screen recording Great for protecting sensitive content without hurting UX.\n3.2 Android 10–12 — Workarounds # 3.2.1 Activity Lifecycle Approach # override fun onPause() { window.addFlags(FLAG_SECURE) } override fun onResume() { window.clearFlags(FLAG_SECURE) } Result: # Works when pressing Home Does not work reliably when pressing Recents (especially Samsung) 3.2.2 Using onWindowFocusChanged # override fun onWindowFocusChanged(hasFocus: Boolean) { if (hasFocus) window.clearFlags(FLAG_SECURE) else window.addFlags(FLAG_SECURE) } This fails because:\nDialog steals focus Keyboard steals focus Notifications steal focus Permission dialogs steal focus Multi-window mode System UI interactions Your activity is still visible but loses focus → flicker and broken UX.\n3.3 Android 10+ — Better Approach: onTopResumedActivityChanged() # override fun onTopResumedActivityChanged(isTopResumedActivity: Boolean) { super.onTopResumedActivityChanged(isTopResumedActivity) if (isTopResumedActivity) { window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) } else { window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } } Why it’s better: # Not triggered by dialogs Not affected by keyboard Not affected by system UI Only fires when entering/exiting foreground Limitations: # Only Android 10+ Toggling FLAG_SECURE may still flicker Split-screen behavior varies per OEM 4. Per-Screen Security in Jetpack Compose # @Composable fun SecureScreenProtection() { val activity = LocalActivity.current val allowScreenshot = LocalAllowScreenshot.current DisposableEffect(Unit) { if (!allowScreenshotForDev()) { activity?.window?.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE ) } onDispose { if (allowScreenshot) { activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) } } } } val LocalAllowScreenshot = compositionLocalOf\u0026lt;Boolean\u0026gt; { false } private fun allowScreenshotForDev(): Boolean { return true // or BuildConfig.DEBUG } Apply at app level:\nCompositionLocalProvider( LocalAllowScreenshot provides isScreenshotAllowed, ) { // YourApp() } This allows selective protection without harming UX or debugging.\n5. Summary # Goal Best Approach Block screenshots completely FLAG_SECURE Block Recents preview (Android 13+) setRecentsScreenshotEnabled(false) Block Recents preview (Android 10+) onTopResumedActivityChanged() Detect screenshots (Android 10–13) MediaStore Observer Detect screenshots (Android 14+) ScreenCaptureCallback Per-screen protection Compose SecureScreenProtection() ","date":"1 ธันวาคม 2025","externalUrl":null,"permalink":"/posts/flag-secure/","section":"Posts","summary":"Screenshot security in Android is more complicated than it looks, not just FLAG_SECURE","title":"Behind the Screen: Detecting and Preventing Screenshots in Android","type":"posts"},{"content":"","date":"1 ธันวาคม 2025","externalUrl":null,"permalink":"/tags/screenshot/","section":"Tags","summary":"","title":"Screenshot","type":"tags"},{"content":"","date":"1 ธันวาคม 2025","externalUrl":null,"permalink":"/tags/security/","section":"Tags","summary":"","title":"Security","type":"tags"},{"content":" Introduction # I had the chance to work on an app that required a security test based on OWASP guidelines. Based on the requirement, I was asked to export a list of all the dependencies used in the app in the format name|version|source. Since I was using version.toml with Gradle’s Version Catalog, getting everything into that format wasn’t straightforward. At first, I just used an LLM to help with the formatting—but along the way, I discovered a much simpler approach worth sharing.\nAs the Gradle Version Catalog has been streamlined, managing and sharing dependencies across submodules has become so much easier, but it is now harder to present those dependencies version in a simple manner those data back for the Mobile Application Security Package Analysis.\nBut there is actually an easy way to do that, which can be achieve by a few clicks in Android Studio IDE.\nMethod 1: From Gradle CLI # You can run gradle :app:androidDependencies in Android Studio to get all the compiled dependencies use in the project, this will listed all the dependencies graph under that aar packages with respect to build variants.\nMethod 2: From Android Studio # Or it can be get from File \u0026gt; Project Structure \u0026gt; Dependencies\nOn the right hand side we can see the Resolved Dependencies and each build flavors. For example, I will choose devPermRelease\nYou can see all the resolved dependencies here, you can even highlight all the dependencies in that group and use Copy Shortcuts on your Operating System (Right Click doesn\u0026rsquo;t seemed to work here) and then paste it in your favourite text editor.\nExample of copied dependencies Key Takeaways # Learned how to export a list of dependencies from Gradle Version Catalog in a text format, useful for security reports and audits. This method also works if you need to export dependencies for a specific build variant or product flavor. ","date":"5 กรกฎาคม 2025","externalUrl":null,"permalink":"/posts/mas-report/","section":"Posts","summary":"\u003ch2 class=\"relative group\"\u003eIntroduction \n    \u003cdiv id=\"introduction\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#introduction\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eI had the chance to work on an app that required a security test based on OWASP guidelines. Based on the requirement, I was asked to export a list of all the dependencies used in the app in the format \u003ccode\u003ename|version|source\u003c/code\u003e. Since I was using version.toml with Gradle’s Version Catalog, getting everything into that format wasn’t straightforward. At first, I just used an LLM to help with the formatting—but along the way, I discovered a much simpler approach worth sharing.\u003c/p\u003e","title":"Extract Gradle Dependencies for Mobile App Security (MAS)","type":"posts"},{"content":"","date":"5 กรกฎาคม 2025","externalUrl":null,"permalink":"/tags/gradle/","section":"Tags","summary":"","title":"Gradle","type":"tags"},{"content":" This Post is being built # By using deep links, you can jump from the notifications to a certain routes in NavGraph or even Nested Graph\nPrerequisites # KotlinX.Serialization Navigation Compose Navigating Deep Links with Intents Url Parameters # Using Something # The following DetailPage class is used as an example for the navigation\n@Serializable data class DetailPage( @SerialName(\u0026#34;title_name\u0026#34;) val title: String, @SerialName(\u0026#34;sub_title\u0026#34;) val subTitle: String? = null val id: Int, ) If you want the navDeepLink to parse your URI, you should put it in this format\nappName://DetailPage/{title_name}/{id}?sub_title={sub_title}\nIn the compose navigation the code will look like\ncomposable\u0026lt;DetailPage\u0026gt;( deepLinks = listOf\u0026lt;DetailPage\u0026gt;( basePath = \u0026#34;appName://DetailPage\u0026#34;, ) ) What if the Uri is in different format? # For example, you want to extract the id and name pattern from the uri or using custom uri altogether. together, you might want to write a custom uriPattern in the NavDeepLink.navDeepLink API\nIt can be put together as\ncomposable\u0026lt;DetailPage\u0026gt;( deepLinks = listOf\u0026lt;DetailPage\u0026gt;( basePath = \u0026#34;appName://DetailPage\u0026#34;, ) { uriPattern = \u0026#34;appName://{id}_{title_name}?sub_title={sub_title}\u0026#34; } ) { val args = it.toRoute\u0026lt;DetailPage\u0026gt; } Then args can be retrieve using\n1 2 3 4 5 6 7 8 Or even create something difficult to build like composable\u0026lt;DetailPage\u0026gt;( deepLinks = listOf\u0026lt;DetailPage\u0026gt;( basePath = \u0026#34;appName://DetailPage\u0026#34;, ) { uriPattern = \u0026#34;example://full_name/{first_name}/{last_name}/edit\u0026#34; } ) [!NOTE] This uriPattern might not be type safe, but it offers flexibility for those in need\n","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/posts/deep-link-fcm/","section":"Posts","summary":"\u003cblockquote\u003e\n\n\u003ch2 class=\"relative group\"\u003eThis Post is being built \n    \u003cdiv id=\"this-post-is-being-built\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#this-post-is-being-built\" aria-label=\"จุดยึด\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eBy using deep links, you can jump from the notifications to a certain routes in NavGraph or even Nested Graph\u003c/p\u003e","title":"🚧Integrating Deep Links with Type-Safe Navigation Compose (and custom parsing) and fcm","type":"posts"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/tags/android/","section":"Tags","summary":"","title":"Android","type":"tags"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/categories/android-development/","section":"Categories","summary":"","title":"Android Development","type":"categories"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/tags/compose/","section":"Tags","summary":"","title":"Compose","type":"tags"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/series/deep-link-compose-fcm/","section":"Series","summary":"","title":"Deep-Link-Compose-Fcm","type":"series"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/tags/deep-links/","section":"Tags","summary":"","title":"Deep-Links","type":"tags"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/tags/fcm/","section":"Tags","summary":"","title":"Fcm","type":"tags"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/","section":"Jojonosaurus","summary":"","title":"Jojonosaurus","type":"page"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/tags/navigation/","section":"Tags","summary":"","title":"Navigation","type":"tags"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"21 ธันวาคม 2024","externalUrl":null,"permalink":"/th/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","externalUrl":null,"permalink":"/th/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"}]