<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Darryl Bayliss</title>
    <description>The latest articles on DEV Community by Darryl Bayliss (@darrylbayliss).</description>
    <link>https://dev.to/darrylbayliss</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1136216%2F8c9efceb-a24e-4a51-832e-3383f95c3842.png</url>
      <title>DEV Community: Darryl Bayliss</title>
      <link>https://dev.to/darrylbayliss</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/darrylbayliss"/>
    <language>en</language>
    <item>
      <title>Running a RAG powered language model on Android using MediaPipe</title>
      <dc:creator>Darryl Bayliss</dc:creator>
      <pubDate>Thu, 18 Sep 2025 05:00:00 +0000</pubDate>
      <link>https://dev.to/darrylbayliss/running-a-rag-powered-language-model-on-android-using-mediapipe-4ed4</link>
      <guid>https://dev.to/darrylbayliss/running-a-rag-powered-language-model-on-android-using-mediapipe-4ed4</guid>
      <description>&lt;p&gt;A couple of months ago I gave a talk about &lt;a href="https://www.youtube.com/watch?v=NPLGFi-_OKE" rel="noopener noreferrer"&gt;running on device SLMs in apps&lt;/a&gt;. It was well recieved and refreshing to be able to give a talk where mobile apps can gain an advantage from the rise in language model usage.&lt;/p&gt;

&lt;p&gt;After the talk, I had a few pieces of feedback from attendees along the lines of “It would be good to see an example of an app using an SLM powered by RAG”.&lt;/p&gt;

&lt;p&gt;These are fair comments. The tricky thing here is the lead time it takes to setup a model and show something meaningful to developers in a limited time. Fortunately, it does make for a great blog post!&lt;/p&gt;

&lt;p&gt;So here it is! If you’re looking for steps to add a RAG powered language model to your Android app, this is the post for you!&lt;/p&gt;

&lt;h1&gt;
  
  
  What is RAG?
&lt;/h1&gt;

&lt;p&gt;If you’re unfamilar with the term, RAG stands for &lt;strong&gt;Retrieval Augmented Generation&lt;/strong&gt;. It’s a technique for language models to access external information that isn’t available in their dataset after training. This allows models to be aware of upto date information they can use to provide more accurate answers to prompts.&lt;/p&gt;

&lt;p&gt;Let’s look at a quick example. Imagine you have two language models, one model is using RAG to retrieve external information about capital cities in Europe whilst the other is only relying on its own knowledge. You decide to give both models the following prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an expert on the geography of Europe. 

Give me the name of 3 capital cities.

Also give me an interesting fact for each of them.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result could look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4hs9648j2r24mw4anmf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4hs9648j2r24mw4anmf.png" alt="An Example Prompt" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the diagram illustrates, you’re more likely to get a useful answer from the language model using RAG, instead of the model relying on its own knowledge. You may also notice less hallucinations from the RAG powered model, as it doesn’t need to make up information.&lt;/p&gt;

&lt;p&gt;That’s really all you need to know about RAG at a high level. Next, let’s go deeper and write some code to enable your own RAG powered language model inside your app!&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up your Android App
&lt;/h1&gt;

&lt;p&gt;Let’s say you want to create an app using a language model to play the game of Simon Says. You want the model to be Simon, and use RAG to access a datasource of tasks to help decide what to ask the user. How would you do that?&lt;/p&gt;

&lt;p&gt;The most straight forward way to do that on Android is with &lt;a href="https://ai.google.dev/edge/mediapipe/solutions/guide" rel="noopener noreferrer"&gt;MediaPipe&lt;/a&gt;, a collection of tools to help your app use AI and machine learning techniques. To begin, add the MediaPipe dependency to your app &lt;code&gt;build.gradle&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
  implementation("com.google.mediapipe:tasks-genai:0.10.27")
  implementation("com.google.ai.edge.localagents:localagents-rag:0.3.0")
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you need to add a language model to your test device via your computer. For this example we’ll use Google’s &lt;a href="https://www.kaggle.com/models/google/gemma-3/tfLite/gemma3-1b-it-int4" rel="noopener noreferrer"&gt;Gemma3-1B&lt;/a&gt;, a lightweight language model that holds 1 Billion parameters worth of information.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Side Note: Before downloading the model, you may have to sign upto Kaggle and agree to Google’s AI Terms and Conditions.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the model is downloaded. It’s time to add the model to your device. You can do this via adb:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ adb shell mkdir -p /data/local/tmp/slm/ # Create a folder to store the model
$ adb push output_path /data/local/tmp/slm/gemma3-1B-it-int4.task # Copy the model over to the file

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you can use the &lt;a href="https://developer.android.com/studio/debug/device-file-explorer" rel="noopener noreferrer"&gt;File Explorer&lt;/a&gt; using Android Studio to create the folders yourself and drag the model onto your device.&lt;/p&gt;

&lt;p&gt;With the model added you can continue building the RAG pipeline to feed Gemma with information. Next, let’s look at adding the information gemma will rely to answer prompts.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating Embeddings
&lt;/h1&gt;

&lt;p&gt;The information language models rely on to perform RAG is not the same information you pass into it. Models require information to be in a specific format called &lt;strong&gt;Embeddings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These are mathematical strings that represent the text’s semantic meaning. When the model recieves a prompt it will use it to search for the most relevant information matching it, and use it alongside its own information to provide an answer. These mathematical strings are created by a tool called an &lt;strong&gt;Embedder&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Embeddings are a whole subject on their own you are encouraged to read about. For this post you only need to know how to create them. You can do this on device by using the &lt;a href="https://huggingface.co/litert-community/Gecko-110m-en" rel="noopener noreferrer"&gt;Gecko Embedder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, download the &lt;code&gt;sentencepiece.model&lt;/code&gt; tokenizer and the &lt;code&gt;Gecko_256_f32.tflite&lt;/code&gt; embedder model files to your computer. Then push them to your device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ adb push sentencepiece.model /data/local/tmp/slm/sentencepiece.model # Push the tokenizer to the device
$ adb push Gecko_256_f32.tflite /data/local/tmp/slm/Gecko_256_f32.tflite # Push the embedder model to the device

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the embedder installed to your device, it’s time to provide a sample file to create the embeddings from. In Android Studio, in your app module &lt;code&gt;assets&lt;/code&gt; folder, create a file called &lt;code&gt;simon_says_responses.txt&lt;/code&gt;. Then in the file add the following text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;chunk_splitter&amp;gt;
Go for a walk
&amp;lt;chunk_splitter&amp;gt;
Jump and down 10 times
&amp;lt;chunk_splitter&amp;gt;
Sing your favourite song!
&amp;lt;chunk_splitter&amp;gt;
Text your best friend a funny meme
&amp;lt;chunk_splitter&amp;gt;
Do 10 press ups!
&amp;lt;chunk_splitter&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file contains a couple of different responses one could give in a game of Simon Says, each split up with a &lt;code&gt;&amp;lt;chunk_splitter&amp;gt;&lt;/code&gt; tag. This gives the embedder a signal to know how to separate each response when splitting the text into embeddings.&lt;/p&gt;

&lt;p&gt;This process is called &lt;strong&gt;chunking&lt;/strong&gt; , and can have a large effect on how well RAG performs via the language model. Experimentation with different sized chunks and responses is encouraged to find the right combination for your needs!&lt;/p&gt;

&lt;p&gt;One thing to consider is app storage. Remember you’ve already installed a language model and an embedder onto your device. These take up gigabytyes of space, so make sure not to further bloat a device by using a text file that is too large!&lt;/p&gt;

&lt;p&gt;You want to consider storing the sample file in the cloud and downloading it via the network to reduce issues with storage.&lt;/p&gt;

&lt;p&gt;With the text file in place, it’s time to initialise the embedder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
private const val GeckoEmbedderPath = "/data/local/tmp/slm/gecko_256_f32.tflite"
private const val TokenizerModelPath = "/data/local/tmp/slm/sentencepiece.model"
private const val UseGpuForEmbeddings = true

val embedder: Embedder&amp;lt;String&amp;gt; = GeckoEmbeddingModel(
    GeckoEmbedderPath,
    TokenizerModelPath,
    UseGpuForEmbeddings,
  )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The embedder takes three parameters, the path of the embedder and the tokenizer on the device, with a final parameter to set whether the embedder can use the device GPU when creating embeddings.&lt;/p&gt;

&lt;p&gt;Setting this to true will speed up the creation of the embeddings if a GPU is available. Make sure to check the device capabilities before deciding to enable this value.&lt;/p&gt;

&lt;p&gt;Next, create an instance of the language model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private const val GemmaModelPath = "/data/local/tmp/slm/gemma3-1B-it-int4.task"

val llmInferenceOptions = LlmInferenceOptions.builder()
            .setModelPath(GemmaModelPath)
            .setPreferredBackend(LlmInference.Backend.CPU) // Change to GPU if you have a GPU powered device.
            .setMaxTokens(1200)
            .build()

val llmInferenceSessionOptions = LlmInferenceSessionOptions.builder()
            .setTemperature(0.6f)
            .setTopK(5000)
            .setTopP(1f)
            .build()

val languageModel = MediaPipeLlmBackend(
            context, // This is the application context
            languageModelOptions,
            languageModelSessionOptions)

    languageModel.initialize().get()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are alot of parameters involved above, don’t worry about these for now. We’ll come back to them later on.&lt;/p&gt;

&lt;p&gt;With the language model created, you can focus back on the embeddings. The model needs a place to retrieve the embeddings from each time it recieves a prompt.&lt;/p&gt;

&lt;p&gt;MediaPipe provides an SQLite &lt;strong&gt;Vector Store&lt;/strong&gt; , which is a common tool to store embeddings. Let’s create one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private const val PromptTemplate: String = """
    You are Simon in a game of Simon Says.

    Your task is to ask the player to perform a task from the following list: {0}.

    Your response must only contain the task that the player must do.

    Your response must be based on the players request: {1}. 

    Do not ask the player to do the same thing twice.

    You must not ask the player to do anything that is dangerous, unethical or unlawful.
"""

val chainConfig = ChainConfig.create(
    languageModel, PromptBuilder(PromptTemplate),
    DefaultSemanticTextMemory(
    SqliteVectorStore(768), embedder
    )
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, everything begins to link together as the language model and embedder are both passed into the ChainConfig. The &lt;code&gt;786&lt;/code&gt; is the size of each “vector” the database can store. The &lt;code&gt;PromptBuilder&lt;/code&gt; is used to provide a prompt to help drive the RAG process.&lt;/p&gt;

&lt;p&gt;Finally, let’s load the text file we created earlier in the assets folder into the embedder. First, load the file from the device and split the text into a list of strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// This is an extension function to read the file from disk.
val gameResponses: List&amp;lt;String&amp;gt; = context.getTextFromFile("simon_says_responses.txt")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, load the responses into the chainConfig created earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chainConfig.semanticMemory.getOrNull()
    ?.recordBatchedMemoryItems(ImmutableList.copyOf(gameResponses))
    ?.get()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on your device and the size of the text file this can take a couple of seconds to a few minutes complete. A good rule of thumb is to prevent the language model from being used whilst this occurs. You could also run this operation on an IO thread to avoid blocking the main thread.&lt;/p&gt;

&lt;p&gt;With that done, you’ve just converted your textfile into a set of embeddings kept in a vector store. You’ve also linked your language model to the store so it can now retrieve information!&lt;/p&gt;

&lt;p&gt;The next section will show how to use those embeddings by passing the language model your prompts.&lt;/p&gt;

&lt;h1&gt;
  
  
  Passing Prompts to your RAG Powered Model
&lt;/h1&gt;

&lt;p&gt;With most of the complex setup complete, passing a prompt into the language model is surprisingly easy. First you need to create a &lt;strong&gt;RetrievalAndInferenceChain&lt;/strong&gt; using the chain config and invoke it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val retrievalAndInferenceChain = RetrievalAndInferenceChain(chainConfig)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a request and pass it into the chain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val prompt = "Tell me something to do Simon involving jumping!"

val retrievalRequest =
    RetrievalRequest.create(
        prompt,
        RetrievalConfig.create(
            50, // topK
            0.1f, // minSimilarityScore
            RetrievalConfig.TaskType.RETRIEVAL_QUERY
        )
    )

val response = retrievalAndInferenceChain!!.invoke(retrievalRequest).get().text

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that the language model will process the prompt. During processing it will refer to the embeddings in your vector store to provide what it thinks is the most accurate answer.&lt;/p&gt;

&lt;p&gt;As you would expect, what you ask the model will result in different responses. As the embeddings contain a range of simon says responses, it’s likely you will get a good response!&lt;/p&gt;

&lt;p&gt;What about the cases where you recieve an unexpected result? At this point you need to go back and fine tune the parameters of your objects from the previous section.&lt;/p&gt;

&lt;p&gt;Let’s do that in the next section.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fine tuning your RAG Powered Language Model
&lt;/h1&gt;

&lt;p&gt;If there’s one thing we’ve learned over the years from Generative AI, it’s that it isn’t an exact science.&lt;/p&gt;

&lt;p&gt;You’ve no doubt seen stories where language models have “misbehaved” and produced a less than desired result, causing embarassment and reputational damage to companies.&lt;/p&gt;

&lt;p&gt;This is the result of not enough testing being performed to ensure the responses from a model are expected. Testing not only helps avoid embarassment, it can also help to experiment with your language model output so it works even better!&lt;/p&gt;

&lt;p&gt;Let’s take a look at what levers we can adjust to fine tune our RAG powered language model. Starting with &lt;code&gt;LLmInferenceOptions.Builder&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LlmInferenceOptions.builder()
    .setModelPath(GemmaModelPath)
    .setPreferredBackend(LlmInference.Backend.CPU) // Change to GPU if you have a GPU powered device.
    .setMaxTokens(1200)
    .build()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first parameter that can be changed is the &lt;code&gt;setMaxTokens()&lt;/code&gt; parameter. This is the amount of “input” or “output” the language model can handle. The larger the value, the more data the language model can handle at once. Resulting in better answers.&lt;/p&gt;

&lt;p&gt;In our example, this means the model can handle a text input and generate an output that consists of 1200 tokens. If we wanted to handle less text and also generate a smaller response, we could set &lt;code&gt;MaxTokens&lt;/code&gt; to a smaller value.&lt;/p&gt;

&lt;p&gt;Be careful with this value, as you could find your model unexpectedly handling more tokens that it expects. Causing an app crash.&lt;/p&gt;

&lt;p&gt;Let’s move onto &lt;code&gt;LlmInferenceSessionOptions.Builder()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;llmInferenceSessionOptions = LlmInferenceSessionOptions.builder()
    .setTemperature(0.6f)
    .setTopK(5000)
    .setTopP(1f)
    .build()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, you can set a couple of different parameters. &lt;code&gt;.setTemperature()&lt;/code&gt;, &lt;code&gt;.setTopK()&lt;/code&gt;, and &lt;code&gt;.setTopP()&lt;/code&gt;. Let’s dive them into each of them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.setTemperature()&lt;/code&gt; can be thought of how “random” the responses from the language model can be. The lower the value, the more “predictable” the responses from the model can be. The higher the value the more “creative” the responses will be, leading to more unexpected responses.&lt;/p&gt;

&lt;p&gt;For this example it’s set to &lt;code&gt;0.6&lt;/code&gt;, meaning the model will provide semi-creative, but not unimaginable responses.&lt;/p&gt;

&lt;p&gt;The temperature is a good value to experiment with, as you may find different values provide better responses depending on your use case. In a game of Simons Says, some creativity is welcome!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.setTopK()&lt;/code&gt; is a way of saying “only consider the top K results to return the user”. Language models whilst processing a prompt generate a number of responses, potentially thousands!&lt;/p&gt;

&lt;p&gt;Each of these responses are given a probability as to how likely they are to be the right answer. To limit the amount of responses to consider, the &lt;code&gt;topK&lt;/code&gt; value can be set to help focus the model. If you’re happy with less probabable responses being considered, you would want to set this value high.&lt;/p&gt;

&lt;p&gt;Similar to the temperarture property, this is a good property to experiment with. You may find the model works better with less or more responses to consider, depending on your needs.&lt;/p&gt;

&lt;p&gt;For a game of Simon Says, we want the model to be thinking about alot of different responses to keep the game fresh. So &lt;code&gt;5000&lt;/code&gt; seems like a good value.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.setTopP()&lt;/code&gt; builds upon the limit set by &lt;code&gt;topK&lt;/code&gt; by saying “only consider results that have a probability of P”. As mentioned earlier, language models assign a probablility to each response it generates as to how likely it can be. Setting &lt;code&gt;topP&lt;/code&gt; means the model can easily discard any responses that don’t have the minimum probability needed.&lt;/p&gt;

&lt;p&gt;To show an example, if the model had a &lt;code&gt;topP&lt;/code&gt; set to &lt;code&gt;0.4&lt;/code&gt; and was considering the following responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Simons Says clap 5 times! = 0.7 // This response has a probablility of 0.7 of being correct

Simons Says jump up and down! = 0.5

Find a car and drive it. = 0.3

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first two responses would be considered because the probability is higher than &lt;code&gt;0.4&lt;/code&gt;. The response about the car would be discarded, as it only has a probability of &lt;code&gt;0.3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Similar to &lt;code&gt;temperature&lt;/code&gt; and &lt;code&gt;topK&lt;/code&gt;, &lt;code&gt;topP&lt;/code&gt; allows you to define how creative your language model can be. If you want to consider less probable responses to prompts, setting a low P value will help.&lt;/p&gt;

&lt;p&gt;In our example, it’s set to &lt;code&gt;1.0&lt;/code&gt;. That’s because we want the model to be absolutely sure about its responses. It’s playing a childrens game after all!&lt;/p&gt;

&lt;p&gt;Experimenting with these values will generate very different results from your language models. Try them out and see what happens!&lt;/p&gt;

&lt;h1&gt;
  
  
  Where To Go Next?
&lt;/h1&gt;

&lt;p&gt;I hope you’ve enjoyed this walkthrough of how to add a RAG powered language model to your Android App! If you’re looking to learn more about RAG and Android, here are a few links I recommend:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simons Says App:&lt;/strong&gt; Clone and run the &lt;a href="https://github.com/DarrylBayliss/Simon-Says-RAG-Android" rel="noopener noreferrer"&gt;sample code&lt;/a&gt; for this blog post to see it in action. It shows how to setup the RAG pipeline with Gemma using Android architecture best practices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MediaPipe RAG&lt;/strong&gt; : Check out the RAG section on the MediaPipe &lt;a href="https://ai.google.dev/edge/mediapipe/solutions/genai/rag" rel="noopener noreferrer"&gt;Google Developer docs&lt;/a&gt; Highly recommended reading.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Setting Temperature, TopK, and TopP in LLMs&lt;/strong&gt; : Learn more about how setting the Temperature, TopK, and TopP values can &lt;a href="https://rumn.medium.com/setting-top-k-top-p-and-temperature-in-llms-3da3a8f74832" rel="noopener noreferrer"&gt;help control&lt;/a&gt; the results from language models. Another highly recommended article.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>android</category>
      <category>machinelearning</category>
      <category>ai</category>
    </item>
    <item>
      <title>Multiplatform Development for Apple Devices</title>
      <dc:creator>Darryl Bayliss</dc:creator>
      <pubDate>Tue, 28 Jan 2025 06:00:00 +0000</pubDate>
      <link>https://dev.to/darrylbayliss/multiplatform-development-for-apple-devices-nk0</link>
      <guid>https://dev.to/darrylbayliss/multiplatform-development-for-apple-devices-nk0</guid>
      <description>&lt;p&gt;In June 2023 as Apple announced the Vision Pro I had an idea that could work well on the headset. A collection of looped videos playing alongside your day to day usage.&lt;/p&gt;

&lt;p&gt;I already had an app that could do this called &lt;a href="https://apps.apple.com/us/app/christmas-chill/id1060247339" rel="noopener noreferrer"&gt;Christmas Chill&lt;/a&gt;, something &lt;a href="https://www.darrylbayliss.net/three-weeks-of-apple-tv/" rel="noopener noreferrer"&gt;I built&lt;/a&gt; when the 1st Apple TV supporting an App Store was made available. It features a collection of looped videos you can use as a festive backdrop.&lt;/p&gt;

&lt;p&gt;Each year over Winter I spend a couple of days to improve it, adding new content and improving the codebase. One of the larger changes made to the project came in December 2023, when the UI was migrated from UIKit and Storyboards to SwiftUI.&lt;/p&gt;

&lt;p&gt;Well, mostly migrated. I needed an AVPlayer backed view wrapped in a &lt;a href="https://developer.apple.com/documentation/swiftui/uiviewrepresentable" rel="noopener noreferrer"&gt;UIViewRepresentable&lt;/a&gt;. A great API providing interoperability between UIKit and SwiftUI, if you’re ever in need.&lt;/p&gt;

&lt;p&gt;I had been somewhat hesitant to migrate sooner as I had a good understanding of Declarative UI and its concepts from other work with &lt;a href="https://www.darrylbayliss.net/building-ev-buddy/" rel="noopener noreferrer"&gt;React&lt;/a&gt; and &lt;a href="https://www.darrylbayliss.net/jetpack-compose-for-maps/" rel="noopener noreferrer"&gt;Jetpack Compose&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That changed for me with Apple’s introduction of the Apple Vision Pro and its support for SwiftUI. Christmas Chill has been a nice project to keep my Apple Dev knowledge upto date, and I was keen to gain experience expanding the app to different devices.&lt;/p&gt;

&lt;p&gt;Once the 2023 migration to SwiftUI for Christmas Chill was complete I set out in 2024 to add support for the Vision Pro. Below is how I went about it and what I recommend if you were trying to do the same for your own apps.&lt;/p&gt;

&lt;h1&gt;
  
  
  Adding A New Platform
&lt;/h1&gt;

&lt;p&gt;To start, the project needs to be able to build for Vision Pro as a destination, doing this is surprisingly easy! Inside Xcode, select the &lt;code&gt;.xcodeproj&lt;/code&gt; file and under the &lt;strong&gt;Supported Destinations&lt;/strong&gt; dropdown, click the plus button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8tvalfidhjamzw12tdqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8tvalfidhjamzw12tdqw.png" alt="Add Destination" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A dropdown of all the available Apple platforms appears. Hover over the desired platform to add as a destination, in this case Apple Vision, and then click Apple Vision in the newly appearing section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxlva4d6osgrw9qu21r3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxlva4d6osgrw9qu21r3.png" alt="Add Vision Pro Destination" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A small popup will appear to inform you of changes Xcode needs to make to the target. Click &lt;strong&gt;Enable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1ysaqylr5ub68fw2y1k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1ysaqylr5ub68fw2y1k.png" alt="Enable Destination Support" width="586" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, build the app using the visionOS Simulator. If you have a Vision Pro to hand you can find instructions on how to install it onto your device &lt;a href="https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;During compilation, it’s likely Xcode will find compiler errors and / or the app will crash. This is expected and a practice in being patient. From this point you need to fix the errors in your project until the app compiles and no longer crashes.&lt;/p&gt;

&lt;p&gt;In my case this took around 30 minutes, partially thanks to doing the hard work of migrating the app from UIKit to SwiftUI previously!&lt;/p&gt;

&lt;h1&gt;
  
  
  Conditional Compilation Blocks
&lt;/h1&gt;

&lt;p&gt;SwiftUI at its core is a multiplatform framework, meaning just by compiling SwiftUI code for a different platform it will alter its appearance. Taking into account platform styling and various methods of interaction.&lt;/p&gt;

&lt;p&gt;Whilst this helps to make quick progress during development, you may want more control over how the app appears and take advantage of each individual platforms strengths. A good example is the Vision Pro’s Immersion capabilities, SwiftUI provides an API for this via &lt;a href="https://developer.apple.com/documentation/SwiftUI/ImmersiveSpace" rel="noopener noreferrer"&gt;ImmersiveSpace&lt;/a&gt;, an API only available for visionOS.&lt;/p&gt;

&lt;p&gt;If you tried to use this API whilst compiling the project for Apple TV, Xcode will throw an error informing this API is not available.&lt;/p&gt;

&lt;p&gt;So what is the solution to avoid this situation? The answer comes from using &lt;a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Conditional-Compilation-Block" rel="noopener noreferrer"&gt;Conditional Compilation Blocks&lt;/a&gt;. Compilation Blocks are sections of code providing instructions for when the compiler should compile code within the block.&lt;/p&gt;

&lt;p&gt;Whilst they support a variety of conditions, the most useful for our needs is detecting which platform the code is being compiled for. You can do this with just a few lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var body: some Scene {
    #if os(tvOS)
        WindowGroup {
            HStack {
                Text("I am running on tvOS!")
            }
        }
    #elseif os(visionOS)
        ImmersiveSpace(id: "MyImmersiveSpace") {

        }
    #endif
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A nice feature Xcode does to support conditional compilation blocks is make clear what code will compile depending on the platform selected for compilation. It will also slightly fade out code that won’t be compiled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwoa7br8dkhxvxfup2ug.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmwoa7br8dkhxvxfup2ug.gif" alt="Build Phases" width="412" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Dependency Injection via Build Phases
&lt;/h1&gt;

&lt;p&gt;One of the more useful tricks I’ve found is using the &lt;strong&gt;Compile Sources&lt;/strong&gt; and &lt;strong&gt;Copy Bundle Resources&lt;/strong&gt; build phases as a form of dependency injection. These are processes run when the app is built and can be found under the &lt;strong&gt;Build Phases&lt;/strong&gt; tab in the Xcode Project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnvf28uvenhbcth87i06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnvf28uvenhbcth87i06.png" alt="Build Phases" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compile Sources&lt;/strong&gt; does the heavy lifting of compiling your source code into machine code. Whether its Swift, Objective-C, or even C/C++.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copy Bundle Resources&lt;/strong&gt; copies all related resources for the app target into the &lt;strong&gt;App Bundle&lt;/strong&gt;. A &lt;a href="https://developer.apple.com/documentation/foundation/bundle" rel="noopener noreferrer"&gt;container of sorts&lt;/a&gt; for all the apps code and resources including images, videos, localisable strings, and more.&lt;/p&gt;

&lt;p&gt;These two build phases give alot of flexibility to apps as each new target provides their own build phases, including the two steps above. Whitelabel apps that provide a way for businesses to customise their content use this technique, amongst others.&lt;/p&gt;

&lt;p&gt;You may find you want to provide different content for your own apps, depending on what platform they run on. Let’s use these build phases to our advantage and provide two different sources of content to do that.&lt;/p&gt;

&lt;p&gt;First, let’s use a &lt;a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols/" rel="noopener noreferrer"&gt;Swift Protocol&lt;/a&gt; to provide a contract that expects to be fulfilled by a struct or class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protocol ContentManager {

    var content: [Content] { get }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let’s take a look at two implementers of the protocol. Here is the first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class TargetAppAContentManager : ContentManager {

    var content: [Content] {

            return [
                Content(name: TargetAppAContentIdentifier.videoOneName.rawValue,
                        image: TargetAppAImagePreviewIdentifier.videoOnePreview.rawValue,
                        video: TargetAppAImageVideoIdentifier.videoOneVideo.rawValue),
                Content(name: TargetAppAContentIdentifier.videoTwoName.rawValue,
                        image: TargetAppAImagePreviewIdentifier.videoTwoPreview.rawValue,
                        video: TargetAppAImageVideoIdentifier.videoTwoVideo.rawValue),
                Content(name: TargetAppAContentIdentifier.videoThreeName.rawValue,
                        image: TargetAppAImagePreviewIdentifier.videoThreePreview.rawValue,
                        video: TargetAppAImageVideoIdentifier.videoThreeVideo.rawValue),
        ]

        return contentToShow
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TargetAppAContentManager&lt;/code&gt; is the concrete implementation used for the first app target. It provides an array of &lt;code&gt;Content&lt;/code&gt;, which refers to resource names found in the app bundle for the target.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class TargetAppBContentManager : ContentManager {

    var content: [Content] {

        return [
            Content(name: TargetAppBContentIdentifier.videoOneName.rawValue,
                    image: TargetAppBImagePreviewIdentifier.videoOnePreview.rawValue,
                    video: TargetAppBImageVideoIdentifier.videoOneVideo.rawValue),
            Content(name: TargetAppBContentIdentifier.videoTwoName.rawValue,
                    image: TargetAppBImagePreviewIdentifier.videoTwoPreview.rawValue,
                    video: TargetAppBImageVideoIdentifier.videoTwoVideo.rawValue),
            Content(name: TargetAppBContentIdentifier.videoThreeName.rawValue,
                    image: TargetAppBImagePreviewIdentifier.videoThreePreview.rawValue,
                    video: TargetAppBImageVideoIdentifier.videoThreeVideo.rawValue),
        ]
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next is &lt;code&gt;TargetAppBContentManager&lt;/code&gt;, the concrete implementation used for the second app target. It looks very similar to the first implementation, except for App B the identifiers are different.&lt;/p&gt;

&lt;p&gt;With both implementations created, you can now refer to them indirectly in your code by setting the type of the object to &lt;code&gt;ContentManager&lt;/code&gt;. Check out the example ViewModel below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Observable class VideoListViewModel {

    var contentManager: ContentManager

    init(contentManager: ContentManager) {
        self.contentManager = contentManager
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ViewModel expects a type of &lt;code&gt;ContentManager&lt;/code&gt; to passed in via its initialiser. The ViewModel can be passed ether type of &lt;code&gt;ContentManager&lt;/code&gt; and continue to function as expected. This also means the ViewModel can be reused across both app targets.&lt;/p&gt;

&lt;p&gt;The last thing to do is to ensure the correct ContentManager is added to the Compile Sources phase. In this case, App A is passed &lt;code&gt;TargetAppAContentMananger&lt;/code&gt; as part of its sources, and App B is passed &lt;code&gt;TargetAppBContentManager&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foz8fpl1b7krhpa2967ap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foz8fpl1b7krhpa2967ap.png" alt="Add Content Manager to Compile Sources Build Phase" width="800" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Adding App Bundle Resources
&lt;/h1&gt;

&lt;p&gt;The last thing left to do is to ensure each app Bundle contain resources with names that match the identifiers used by the app. The easy way is to check the &lt;code&gt;Copy Bundle Resources&lt;/code&gt; build phase of each app target and ensure the resources are referred to by the content manager. If not then drag them from your Xcode project into the copy resources phase.&lt;/p&gt;

&lt;p&gt;This takes a little bit of time and care to test, as you don’t recieve a compile time error if a resource being referred to isn’t available in the bundle. During runtime you will get a crash!&lt;/p&gt;

&lt;p&gt;A good way to automate the check is to write a unit test to confirm all resources being referred to by &lt;code&gt;ContentManager&lt;/code&gt; are stored in the bundle. If the test fails when run then you know there is a missing resource in the bundle.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where To Go Next?
&lt;/h1&gt;

&lt;p&gt;If you’ve got this far you should have a good idea of how to bring your app to other Apple Platforms.&lt;/p&gt;

&lt;p&gt;To round off this post, I will leave you with a couple of tips and resources I recommend:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;If adding Apple Vision support to an existing app, first migrate as much of your code from UIKit to SwiftUI as possible. Having seen the speed of an existing app working on Vision Pro when migrated to SwiftUI, it is a useful to be able to rely on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read through Apple’s guidance on &lt;a href="https://developer.apple.com/documentation/visionos/bringing-your-app-to-visionos" rel="noopener noreferrer"&gt;bring existing apps to visionOS&lt;/a&gt;. It provides useful tips and suggestions on how to do it and how to take advantage of visionOS features.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you’re thinking of starting a new multiplatform app yourself there is a Multiplatform tab available in Xcode, providing a number of app templates to use. There is also &lt;a href="https://developer.apple.com/videos/play/wwdc2022/110371/" rel="noopener noreferrer"&gt;a video from WWDC 2022&lt;/a&gt; on the subject.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you would like to see examples of apps working across multiple platforms, I recommend checking out my personal apps &lt;a href="https://apps.apple.com/us/app/christmas-chill/id1060247339" rel="noopener noreferrer"&gt;Christmas Chill&lt;/a&gt; and &lt;a href="https://apps.apple.com/gb/app/ocean-chill/id6476815660" rel="noopener noreferrer"&gt;Ocean Chill&lt;/a&gt;. These are two apps working across tvOS and Vision Pro, built from a single codebase. (tvOS support for Ocean Chill coming soon!)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ios</category>
      <category>macos</category>
      <category>tvos</category>
      <category>visionos</category>
    </item>
    <item>
      <title>Getting Setup With Xcode Cloud</title>
      <dc:creator>Darryl Bayliss</dc:creator>
      <pubDate>Tue, 20 Aug 2024 05:00:00 +0000</pubDate>
      <link>https://dev.to/darrylbayliss/getting-setup-with-xcode-cloud-118e</link>
      <guid>https://dev.to/darrylbayliss/getting-setup-with-xcode-cloud-118e</guid>
      <description>&lt;p&gt;Xcode Cloud is a Continuous Integration and Continuous Delivery (CI / CD) platform provided by Apple. It automates away the hard work of building your apps, testing them, signing them, and pushing them to a destination.&lt;/p&gt;

&lt;p&gt;Why use Xcode Cloud I hear you say? If you’ve been through the process of manually archiving your app, then submitting it to iTunes Connect, you’ll know these steps take time.&lt;/p&gt;

&lt;p&gt;With Xcode Cloud, Apple makes it easy to automate this with a few button clicks through Xcode. Let’s take a look at how to do this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Xcode Cloud
&lt;/h2&gt;

&lt;p&gt;In order to use Xcode Cloud, you need to have a few things setup. The main requirements are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enrollment in the Apple Developer Program&lt;/li&gt;
&lt;li&gt;An app record on App Store Connect&lt;/li&gt;
&lt;li&gt;Your Developer Apple ID added to Xcode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your project also needs to be stored in a &lt;a href="https://developer.apple.com/documentation/xcode/requirements-for-using-xcode-cloud#Developer-account-requirements" rel="noopener noreferrer"&gt;git repository&lt;/a&gt;. Xcode Cloud makes heavy use of git as a trigger to know when to start running.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://developer.apple.com/documentation/xcode/requirements-for-using-xcode-cloud#Developer-account-requirements" rel="noopener noreferrer"&gt;complete list&lt;/a&gt; of requirements is available if you find yourself needing more.&lt;/p&gt;

&lt;p&gt;Once you’re setup, it’s time to setup a workflow. Along the top of Xcode, click the git branch so a popup appears. You’ll see an option called &lt;strong&gt;Create Workflow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzqykzvhb59u7bi6lden.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzqykzvhb59u7bi6lden.png" alt="Create Workflow Popup" width="736" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the option and Xcode will show a new window, asking which product to create a workflow for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzf59hxm0xa4686a0lol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzf59hxm0xa4686a0lol.png" alt="Choose Product" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select an app and click &lt;strong&gt;Next&lt;/strong&gt;. You’ll be taken to the next screen to customise your workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Workflow
&lt;/h2&gt;

&lt;p&gt;The workflow screen contains a few different sections. First, it shows the &lt;strong&gt;General&lt;/strong&gt; section where you can provide infomation about your workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtuo01ro113z2zq22mu0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtuo01ro113z2zq22mu0.png" alt="Workflow General Section" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, you can give your workflow a name and a description on what it does. This is useful for yourself as a solo developer, or as a team where you have multiple engineers who need to use the same workflow.&lt;/p&gt;

&lt;p&gt;You can also restrict the workflow from being edited in this section, ensuring workflows aren’t tampered with by other team members without the right permissions.&lt;/p&gt;

&lt;p&gt;The next section is the &lt;strong&gt;Environment&lt;/strong&gt; section. Here, you can select what version of Xcode and macOS your workflow will use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnn9sf8baks5gvtluw1yr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnn9sf8baks5gvtluw1yr.png" alt="Workflow Environment Section" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Xcode Cloud provides a variety of Xcode versions, including beta versions. This is handy if you want to create multiple workflows building and testing different configurations.&lt;/p&gt;

&lt;p&gt;The environment section also provides the option to clean the workflow before each build. With the option unchecked, each workflow stores the derived data and other infomation from each build to speed up subsequent builds.&lt;/p&gt;

&lt;p&gt;Whilst this can be a good thing, you may want to avoid this depending on your needs. For example, delivering a build to TestFlight for external testing requires a clean build.&lt;/p&gt;

&lt;p&gt;Finally, you can provide environment variables to the workflow. These can be picked up by custom build scripts used by your app to extend workflows. If a variable value needs to be kept secret, you can check the secret tickbox and the value will be hidden.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffceav2mfgsrbjj4g6uff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffceav2mfgsrbjj4g6uff.png" alt="Workflow Environment Secret" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the infomation for the workflow setup and the environment configured, the next step is to create triggers that cause the workflow to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Conditions
&lt;/h2&gt;

&lt;p&gt;The next section is called &lt;strong&gt;Start Conditions&lt;/strong&gt;. Here, you can tell the workflow what conditions should trigger it to run. By default, Xcode sets the start condition to be any change to any file on any branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5zaahgwlt2r555413el.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5zaahgwlt2r555413el.png" alt="Workflow Start Conditions" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s recommended to tweak this as your workflow will run for every commit. One option is to constrain the start condition to run for a certain branch. First, select the &lt;strong&gt;Custom Branches&lt;/strong&gt; radio button, then begin to type the name of the branch. Xcode will search your git branches and suggest branches that match your search.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhsymifnvlke5r5v2k6np.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhsymifnvlke5r5v2k6np.png" alt="Workflow Custom Branch Start Condition" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also choose to trigger workflows based on changes occurring to a specific file or folder, including specific types of files. Multiple files triggers can be added, giving you the ability to build complex trigger rules for file changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjpwh3qmwyssct5g9i7rb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjpwh3qmwyssct5g9i7rb.png" alt="Workflow Custom Branch Files" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you need a different trigger, you can create different start triggers by clicking the + next to the start condition. Xcode provides different triggers for a number of situations.&lt;/p&gt;

&lt;p&gt;These range from changes to git tags, pull request changes, a scheduler for a specific branch, and finally a manually started workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjk0dr9pj1cf5ibadjf9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjk0dr9pj1cf5ibadjf9k.png" alt="Workflow Available Start Conditions" width="416" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One common option across most of the triggers is the ability to &lt;strong&gt;Auto Cancel Builds&lt;/strong&gt;. This is useful if your trigger is likely to start the workflow multiple times. Instead of waiting for each build to finish, Xcode Cloud cancels the older builds and prioritises the latest build.&lt;/p&gt;

&lt;p&gt;This is so useful, it’s enabled by default!&lt;/p&gt;

&lt;p&gt;When you’re happy with your workflow start triggers, you’re ready to move on and set up actions for the workflow to perform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow Actions
&lt;/h2&gt;

&lt;p&gt;As part of any good CI / CD platform, performing actions regularly and repetitively is good practice to ensure your app is builds successfully and you can catch issues quickly.&lt;/p&gt;

&lt;p&gt;Xcode Cloud is no exception, and allows you to choose from a selction of &lt;strong&gt;Actions&lt;/strong&gt;. By default, your workflow includes a &lt;strong&gt;Build&lt;/strong&gt; action to build your chosen app scheme.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjn8nofd3zf4tp3lummc0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjn8nofd3zf4tp3lummc0.png" alt="Workflow Build Action" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If there are issues with the scheme, Xcode Cloud provides a helpful warning so you can address it.&lt;/p&gt;

&lt;p&gt;Similar to start triggers, you can add more build actions that are run as part of the workflow. These include running tests, running the analyzer, and archiving your app before distributing it to TestFlight or the App Store.&lt;/p&gt;

&lt;p&gt;The test and analyse build actions contain a &lt;strong&gt;Requirement&lt;/strong&gt; setting. This setting decides whether the build action must pass for the workflow to continue. For example, if you want the workflow to fail if one of your tests fail then you can enforce this requirement.&lt;/p&gt;

&lt;p&gt;Interestingly, the test build action doesn’t seem to support visionOS at present.&lt;/p&gt;

&lt;p&gt;You can configure as many build actions as needed for your workflow. You could for example, run the tests for each scheme in your project, then build, archive, and distribute each app in one workflow.&lt;/p&gt;

&lt;p&gt;Once you are happy with your build action setup, you can move to the final part of the workflow. Setting up post actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Post Actions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Post Actions&lt;/strong&gt; allow you to perform a couple of actions before the workflow is complete. These include distributing new builds to TestFlight users, notarizing macOS apps, and notifying others of the workflow result through Slack or email.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Notify&lt;/strong&gt; post action gives you a degree of control over what messages are sent. You can choose to recieve all &lt;strong&gt;Build Success&lt;/strong&gt; messages, to be notified when the workflow is fixed from a previous failure, or not to be notified at all!&lt;/p&gt;

&lt;p&gt;Similar options for &lt;strong&gt;Build Failure&lt;/strong&gt; messages are available. You can choose to recieve all failure messages, breaking workflow messages only, or no failure messages at all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgp1dg04vbslwft913v09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgp1dg04vbslwft913v09.png" alt="Workflow Notify Post Action" width="800" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Saving Your Workflow
&lt;/h2&gt;

&lt;p&gt;Once you are happy with your Workflow. Click the &lt;strong&gt;Save&lt;/strong&gt; button at the bottom right of the window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawn5sugweqotkpcchbpm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawn5sugweqotkpcchbpm.png" alt="Workflow Notify Post Action" width="376" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Xcode Cloud will check the workflow and let you know if there are issues that must be addressed. If all is well, the workflow window disappears and takes you back to Xcode.&lt;/p&gt;

&lt;p&gt;Click on the git branch along the top of Xcode again, and this time click &lt;strong&gt;Manage Workflows&lt;/strong&gt;. A new window will appear, showing the workflow you created for the app.&lt;/p&gt;

&lt;p&gt;If you want to edit the workflow, select the workflow and click the gear button in the bottom left corner. You can also click the &lt;code&gt;-&lt;/code&gt; button whilst the workflow is selected to delete it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1c3kzxs0g2exv627lnn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1c3kzxs0g2exv627lnn.png" alt="Manage Workflows Screen" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Shell Scripts In Xcode Cloud
&lt;/h2&gt;

&lt;p&gt;Xcode Cloud provides support for running shell scripts at specific parts of the workflow. This is useful if you need to perform more advanced configuration for your workflow, and allows your workflow to be customised even further.&lt;/p&gt;

&lt;p&gt;Exploring this topic is beyond the scope of this post, however you can find out more in the &lt;a href="https://developer.apple.com/documentation/xcode/writing-custom-build-scripts" rel="noopener noreferrer"&gt;Developer Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing Your Workflow In Xcode And App Store Connect
&lt;/h2&gt;

&lt;p&gt;You can view the results of your workflow in Xcode by clicking the &lt;strong&gt;Report Navigator&lt;/strong&gt; and clicking the &lt;strong&gt;Cloud&lt;/strong&gt; tab. Each workflow will be shown, including the results from each build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdg9wapjos7op790ioq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdg9wapjos7op790ioq0.png" alt="Workflow Report Navigator" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also view and interact with your workflows in App Store Connect by navigating to your App Store listing and selecting the &lt;strong&gt;Xcode Cloud&lt;/strong&gt; tab along the top of the listing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1dqum4uj7t89dvc6i06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1dqum4uj7t89dvc6i06.png" alt="Manage Workflows With App Store Connect" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Costs
&lt;/h2&gt;

&lt;p&gt;Xcode Cloud comes included in every Apple Developer Program Membership. Each membership comes with 25 hours of compute time per month. If you go over then you can choose from a variety of subscription options to recieve more time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusoct512e6n8desxzh8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusoct512e6n8desxzh8z.png" alt="Workflow Report Navigator" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;Xcode Cloud on the surface is a simple to use CI / CD platform for your Apple Apps. Don’t be decieved however, in a few button clicks you can begin to create highly performant workflows that cover the majority of your use cases.&lt;/p&gt;

&lt;p&gt;If you want to learn more, I recommend starting here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/Xcode/Xcode-Cloud" rel="noopener noreferrer"&gt;Xcode Cloud Developer Documentation&lt;/a&gt; The offical documentation covering general usage and in depth topics on how to get the most out of Xcode Cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/xcode/developing-a-workflow-strategy-for-xcode-cloud" rel="noopener noreferrer"&gt;Developing a workflow strategy for Xcode Cloud&lt;/a&gt; Best practice suggestions on how to refine your CI / CD practices using Xcode Cloud&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/xcode/configuring-webhooks-in-xcode-cloud" rel="noopener noreferrer"&gt;Configuring Webhooks with Xcode Cloud&lt;/a&gt; Advice on how to expose Xcode Cloud events into your own tools and services using Webhooks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;If you enjoyed this blog post, consider dropping me a couple of $$$ over on &lt;a href="https://ko-fi.com/darrylbayliss" rel="noopener noreferrer"&gt;Kofi&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also follow me on Social Media to hear more about software engineering by clicking the links below.&lt;/p&gt;

&lt;p&gt;X / Twitter: &lt;a href="https://x.com/darryl_bayliss" rel="noopener noreferrer"&gt;https://x.com/darryl_bayliss&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/darrylbayliss/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/darrylbayliss/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mastodon: &lt;a href="https://mastodon.social/@darryl_bayliss" rel="noopener noreferrer"&gt;https://mastodon.social/@darryl_bayliss&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/DarrylBayliss" rel="noopener noreferrer"&gt;https://github.com/DarrylBayliss&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>macos</category>
      <category>tvos</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Which Map Transformation Should I Use?</title>
      <dc:creator>Darryl Bayliss</dc:creator>
      <pubDate>Thu, 04 Jul 2024 05:00:00 +0000</pubDate>
      <link>https://dev.to/darrylbayliss/which-map-transformation-should-i-use-24ak</link>
      <guid>https://dev.to/darrylbayliss/which-map-transformation-should-i-use-24ak</guid>
      <description>&lt;p&gt;Map transformation functions find common usage in Android development. They are part of the &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/" rel="noopener noreferrer"&gt;Kotlin Standard Library&lt;/a&gt;, a library built by JetBrains to provide standard functionality across Kotlin codebases.&lt;/p&gt;

&lt;p&gt;Inside the Standard Library is a package called &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/" rel="noopener noreferrer"&gt;kotlin.collections&lt;/a&gt;, containing the building blocks for different collections. These include &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/" rel="noopener noreferrer"&gt;Lists&lt;/a&gt;, &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/#kotlin.collections.Map" rel="noopener noreferrer"&gt;Maps&lt;/a&gt;, and &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/" rel="noopener noreferrer"&gt;Sets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The collections package also contain the map transformation functions. These functions take the contents of a collection and transform them into another collection containing the transformed state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---l6Z-G9M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/map_transformation_diagram.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---l6Z-G9M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/map_transformation_diagram.png" alt="Map transformation diagram" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s take a look at some examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Map Transformation
&lt;/h2&gt;

&lt;p&gt;The first transformation is the &lt;code&gt;map()&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;numbersList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 3, 4)&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersMap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"one"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"two"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"three"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;numbersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 3, 4)&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersSet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;numbersSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 3, 4)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function iterates over a collection and applies the transformation to each value within a lambda, before returning a new collection containing the transformed values.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;map()&lt;/code&gt; is available across different types of collections. The reason for this is because &lt;code&gt;map()&lt;/code&gt; is an extension function on the &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-iterable/" rel="noopener noreferrer"&gt;Iterable&lt;/a&gt; interface. Most collections implement &lt;code&gt;Iterable&lt;/code&gt;, meaning they can make use of the map function.&lt;/p&gt;

&lt;p&gt;Map collection types are different. They don’t implement &lt;code&gt;Iterable&lt;/code&gt;, instead they possess a separate &lt;a href="https://github.com/JetBrains/kotlin/blob/037b3697ed635a52c283da7b2bf6ecd0961ce8f4/libraries/stdlib/common/src/generated/_Maps.kt#L125" rel="noopener noreferrer"&gt;extension method&lt;/a&gt; to provide a map function that iterates over each entry and returns a List of results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Map Transformations and Null Values
&lt;/h2&gt;

&lt;p&gt;Map transformations come in different forms. Another useful transformation is the &lt;code&gt;mapNotNull()&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;numbersList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapNotNull&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 4)&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersMap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"one"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"two"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"three"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;numbersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapNotNull&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 4)&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersSet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;numbersSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapNotNull&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 4)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function acts both as a transformation function and a filter for null values. If the transformation inside the lambda results in &lt;code&gt;null&lt;/code&gt; then the value is not added to the new list.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mapNotNull()&lt;/code&gt; is available to collections implementing the &lt;code&gt;Iterable&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;Map types have their own &lt;a href="https://github.com/JetBrains/kotlin/blob/037b3697ed635a52c283da7b2bf6ecd0961ce8f4/libraries/stdlib/common/src/generated/_Maps.kt#L135" rel="noopener noreferrer"&gt;extension function&lt;/a&gt; to provide similar functionality. Returning a list of results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acquiring an Index with Map Transformations
&lt;/h2&gt;

&lt;p&gt;If you need to know the location of the value within the collection being transformed you can use &lt;code&gt;mapIndexed()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;numbersList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapIndexed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 4, 6)&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersMap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"one"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"two"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"three"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;numbersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asIterable&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mapIndexed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 4, 6)&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersSet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;numbersSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapIndexed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 4, 6)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the location of the value (the index) within the collection is passed alongside the value being transformed. &lt;code&gt;mapIndexed()&lt;/code&gt; is available to collections implementing the &lt;code&gt;Iterable&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;Map types don’t have a &lt;code&gt;mapIndexed()&lt;/code&gt; extension function. What you can do though is use the &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/as-iterable.html" rel="noopener noreferrer"&gt;asIterable()&lt;/a&gt; extension to wrap the Map inside an Iterable instance. Then you can use &lt;code&gt;mapIndexed()&lt;/code&gt; without issue.&lt;/p&gt;

&lt;p&gt;If you need to check for null values and also require an index you can also use &lt;a href="https://github.com/JetBrains/kotlin/blob/037b3697ed635a52c283da7b2bf6ecd0961ce8f4/libraries/stdlib/common/src/generated/_Collections.kt#L1576" rel="noopener noreferrer"&gt;mapIndexedNotNull()&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;numbersList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapIndexedNotNull&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 6)&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersMap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"one"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"two"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"three"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;numbersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asIterable&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;mapIndexedNotNull&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 6)&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersSet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;numbersSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapIndexedNotNull&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//listOf(2, 6)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mapIndexedNotNull()&lt;/code&gt; works similarly to &lt;code&gt;mapNotNull()&lt;/code&gt;. It filters away null values within the transformation lambda whilst also passing in the index for the value from the collection. Like other map transformations it exists on all types implementing Iterable.&lt;/p&gt;

&lt;p&gt;Map types can use the &lt;code&gt;asIterable()&lt;/code&gt; function to gain access to &lt;code&gt;mapIndexedNotNull()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Transformations for Map Types
&lt;/h2&gt;

&lt;p&gt;Map types work differently than other collections due to not implementing the &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-collection/" rel="noopener noreferrer"&gt;Collection&lt;/a&gt; or Iterable interfaces. Because of this they have a few of their own transformation functions not available to other types. The first is called &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/map-keys.html" rel="noopener noreferrer"&gt;mapKeys()&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersMap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"one"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"two"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"three"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;numbersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapKeys&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capitalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// mapOf("One" to 1, "Two" to 2, "Three" to 3)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mapKeys()&lt;/code&gt; transforms each key within the map by passing through each Entry of the map. Once all the transformations are complete they are applied to the Map.&lt;/p&gt;

&lt;p&gt;The second function is called &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/map-values.html" rel="noopener noreferrer"&gt;mapValues()&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersMap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"one"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"two"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"three"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;numbersMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapValues&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// mapOf("one" to 2, "two" to 3, "three" to 4)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mapValues()&lt;/code&gt; works in a similar way. It passes through each &lt;code&gt;Entry&lt;/code&gt; of the map and transforms each value. Once all the transformations are complete they are applied to the map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passing Map Transformations to a Destination
&lt;/h2&gt;

&lt;p&gt;If you want to pass applied transformations to a different collection other than the source there are a few functions to help.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersDestinationSet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableSetOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
  &lt;span class="n"&gt;numbersList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbersDestinationSet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbersDestinationList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// setOf(2, 3, 4)&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersMap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"one"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"two"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                         &lt;span class="s"&gt;"three"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersDestinationList2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
  &lt;span class="n"&gt;numbersList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbersDestinationList2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbersDestinationList2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 3, 4)&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersSet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;numbersDestinationList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
  &lt;span class="n"&gt;numbersSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mapTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbersDestinationList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbersDestinationList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// listOf(2, 3, 4)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;mapTo&lt;/code&gt; functions work similarly to &lt;code&gt;map()&lt;/code&gt;. The difference is they write the transformations to the passed in collection. The collection being written doesn’t have to be the same type as the source collection. Useful if you have a usecase where a different collection would be more optimal.&lt;/p&gt;

&lt;p&gt;Map types can’t use &lt;code&gt;mapTo()&lt;/code&gt;. This is because &lt;code&gt;mapTo()&lt;/code&gt; expects to write to a &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-collection/#kotlin.collections.MutableCollection" rel="noopener noreferrer"&gt;MutableCollection&lt;/a&gt;, which Map types don’t inherit from.&lt;/p&gt;

&lt;p&gt;There is a &lt;code&gt;MutableMap&lt;/code&gt; type, however because it doesn’t inherit from &lt;code&gt;MutableCollection&lt;/code&gt; there is no &lt;code&gt;mapTo()&lt;/code&gt; extension available.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Resources
&lt;/h2&gt;

&lt;p&gt;If you’d like to learn more about Kotlin’s transformation methods. I highly recommend &lt;a href="https://kotlinlang.org/docs/collection-transformations.html" rel="noopener noreferrer"&gt;this page&lt;/a&gt; on collection transformations from the Kotlin language documentation.&lt;/p&gt;

&lt;p&gt;As well as covering map transformations it also covers other methods like zipping, association and flattening. These topics are beyond the scope of this post however are nevertheless useful to understand.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>android</category>
      <category>androiddev</category>
      <category>functional</category>
    </item>
    <item>
      <title>Playing Simon Says with Gemma-2b and MediaPipe</title>
      <dc:creator>Darryl Bayliss</dc:creator>
      <pubDate>Sat, 16 Mar 2024 05:00:00 +0000</pubDate>
      <link>https://dev.to/darrylbayliss/playing-simon-says-with-gemma-2b-and-mediapipe-9a</link>
      <guid>https://dev.to/darrylbayliss/playing-simon-says-with-gemma-2b-and-mediapipe-9a</guid>
      <description>&lt;p&gt;A couple of weeks ago I attended Google’s Gemma Developer Day. A day dedicated to Google presenting the capabilities, portability and openness of their newest LLM models called Gemma.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CqOC--DG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/gemma_logo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CqOC--DG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/gemma_logo.png" alt="Gemma Logo" width="486" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These models present an exciting new phase for Generative AI. Models small enough to be deployed on local devices and can still provide an engaging and helpful experience.&lt;/p&gt;

&lt;p&gt;It’s &lt;a href="https://www.slideshare.net/dazindustries/mobile-machine-learning"&gt;been a while&lt;/a&gt; since I looked at Mobile AI and went away from the day feeling inspired. I decided to see what happens when I tried these models in an Android App.&lt;/p&gt;

&lt;h1&gt;
  
  
  The App
&lt;/h1&gt;

&lt;p&gt;I wanted to give Gemma an easy yet novel challenge. Playing a game of Simon Says.&lt;/p&gt;

&lt;p&gt;The rules are simple. One player gets to be Simon. Their role is to give the other players tasks to perform. The player playing as Simon has to say “Simon says” before they give the task.&lt;/p&gt;

&lt;p&gt;I created an app with three screens. An entry screen, an instructions screen, and a chat screen where Gemma can communicate and give tasks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8BPcTB4k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/simon_says_app_screens.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8BPcTB4k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/simon_says_app_screens.png" alt="Simon Says App Screens" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To speed up building the chat screen I found this blog post from &lt;a href="https://medium.com/@meytataliti/building-a-simple-chat-app-with-jetpack-compose-883a240592d4"&gt;Meyta Taliti&lt;/a&gt; extremely helpful.&lt;/p&gt;

&lt;p&gt;With the screens created my next task was to integrate Gemma. For this I relied on a set of tools I learnt about called MediaPipe.&lt;/p&gt;

&lt;h1&gt;
  
  
  MediaPipe
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/mediapipe/solutions/guide"&gt;MediaPipe&lt;/a&gt; is a collection of tools with the goal of simplifying integrating AI models into apps on Android, iOS, and the web. With MediaPipe you have alot of choice depending on your needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F_SeUlqH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/mediapipe_logo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F_SeUlqH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/mediapipe_logo.png" alt="Mediapipe Logo" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to get started quickly MediaPipe provides an API called &lt;strong&gt;Tasks&lt;/strong&gt; for you to call out to AI models. These API’s are split into different areas such as Vision, Text &amp;amp; Audio.&lt;/p&gt;

&lt;p&gt;MediaPipe also provides a collection of pretrained models to embed within your apps. Again, useful for getting starting quickly.&lt;/p&gt;

&lt;p&gt;If you need something more custom and don’t want to create a model from scratch MediaPipe provides a tool called &lt;strong&gt;Model Maker&lt;/strong&gt;. Model Maker uses a process called &lt;a href="https://en.wikipedia.org/wiki/Transfer_learning"&gt;Transfer Learning&lt;/a&gt; to retrain an existing machine learning model and provide it with new data.&lt;/p&gt;

&lt;p&gt;The benefit of this approach is it saves time and requires less training data to create a new model.&lt;/p&gt;

&lt;p&gt;Model Maker can also reduce the size of the created model through this process. Do note that this process causes the model to “forget” some of its existing knowledge.&lt;/p&gt;

&lt;p&gt;The final tool from MediaPipe is &lt;strong&gt;MediaPipe Studio&lt;/strong&gt; , a web application to evaluate and tweak your custom models. Useful if you want to benchmark your models and understand how well they work prior to deployment.&lt;/p&gt;

&lt;p&gt;For our needs we’re going to leverage the LLM Interfence API, a new API for MediaPipe. This allows us to communicate with Gemma and recieve a response.&lt;/p&gt;

&lt;h1&gt;
  
  
  Putting MediaPipe to Work
&lt;/h1&gt;

&lt;p&gt;To use MediaPipe you first need to add it as a gradle dependency to the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"com.google.mediapipe:tasks-genai:0.10.11"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you create an instance of &lt;code&gt;LlmInference&lt;/code&gt;. This is the object you use to communicate with Gemma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;llmInference&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmInference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createFromOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;LlmInference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LlmInferenceOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setModelPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/data/local/tmp/llm/gemma-2b-it-cpu-int8.bin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s important to note the path set using &lt;code&gt;.setModelPath&lt;/code&gt;. This is the where the Gemma model resides on the device. It’s also important the Gemma model used is the &lt;code&gt;gemma-2b&lt;/code&gt; versions. The &lt;code&gt;7b&lt;/code&gt; versions are not yet supported by MediaPipe, more on what this all means later. For now let’s download the model.&lt;/p&gt;

&lt;p&gt;You can download Gemma from &lt;a href="https://www.kaggle.com"&gt;Kaggle&lt;/a&gt;. A website dedicated to Data Scientists and Machine Learning. You need to create an account and accept the Terms &amp;amp; Conditions of use before you can download the models. You can find the Gemma page &lt;a href="https://www.kaggle.com/models/google/gemma"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re following along to this post remember to only download the &lt;code&gt;gemma-2b-it-cpu&lt;/code&gt; versions of the model, under the &lt;code&gt;TensorFlow Lite&lt;/code&gt; tab. You’re on your own if you try the &lt;code&gt;gemma-2b-it-gpu&lt;/code&gt; versions.&lt;/p&gt;

&lt;p&gt;Once the model is downloaded. Use the &lt;strong&gt;Device Explorer&lt;/strong&gt; in Android Studio to import the model to the path set in &lt;code&gt;.setModelPath&lt;/code&gt;. If you’ve changed the path or the model name then make sure to update the path name.&lt;/p&gt;

&lt;p&gt;Once the model is imported you can begin to pass prompts into Gemma using the &lt;code&gt;.generateResponse&lt;/code&gt; method. Here is an example of the prompt I pass to Gemma to play Simon Says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;SimonSaysPrompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
    You are a Simon in a game of Simon Says. Your objective is to ask the player to perform tasks.

    For every task you give, you must prefix it with the words "Simon says".

    You must not ask the player to do anything that is dangerous, unethical or unlawful.

    Do not try to communicate with the player. Only ask the player to perform tasks.
"""&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;gemmaResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llmInference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SimonSaysPrompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’ve used LLMs before and have a basic understanding of Prompt Engineering this should look familar. To err on the side of caution I’ve included precautionary instructions in the prompt. We don’t want Simon asking the user to do anything questionable!&lt;/p&gt;

&lt;p&gt;If you try to run this on a device a couple of things may happen.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The app may take a short while to respond and eventually provide a response.&lt;/li&gt;
&lt;li&gt;The app may crash. Looking in Logcat you’ll see messages about MediaPipe being unable to find the model. Did you set the right model path?&lt;/li&gt;
&lt;li&gt;The app may crash. If you look in Logcat you can see alot of native code logging and infomation about memory being recycled.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My experience fell into the second and third category. Your own experiences may vary if you’ve setup everything correctly and using a high spec physical device.&lt;/p&gt;

&lt;p&gt;If you don’t have ether of these things. There’s another option, increasing the amount of RAM available through the Emulator.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating an Android Emulator with Increased RAM
&lt;/h1&gt;

&lt;p&gt;Increasing the amount of RAM available usually helps in a memory intensive environment so why would a memory hungry LLM be any different? To do this I customised the amount of RAM my Android emulator used.&lt;/p&gt;

&lt;p&gt;If you have an existing emulator you may notice the RAM field is disabled. You can still update the amount of RAM it has available by clicking on the three dots to the right of it in the Device Manager.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Show on Disk&lt;/strong&gt; and then open up the &lt;strong&gt;config.ini&lt;/strong&gt; and &lt;strong&gt;hardware-qemu.ini&lt;/strong&gt; files in a text editor. Change the values of &lt;code&gt;hw.ramSize&lt;/code&gt; in each file. Thanks goes to this &lt;a href="https://stackoverflow.com/questions/40068344/how-can-i-change-the-ram-amount-that-the-android-emulator-is-using"&gt;Stack Overflow&lt;/a&gt; question for giving me the answer on how to do it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2qnSjy8e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/android_emulator_show_on_disk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2qnSjy8e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/android_emulator_show_on_disk.png" alt="Show Android Emulator on Disk" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alternatively you can create a custom emulator by going to the &lt;strong&gt;Device Manager&lt;/strong&gt; in Android Studio, clicking &lt;strong&gt;Create Virtual Device&lt;/strong&gt; and then &lt;strong&gt;New Hardware Profile&lt;/strong&gt;. As part of the options to customise you can select the amount of RAM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--efs0NhS2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/android_emulator_new_hardware_profile.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--efs0NhS2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/android_emulator_new_hardware_profile.png" alt="Create New Hardware Profile" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I found 8GB of RAM to work relatively well. I also tried my luck with 22GB of RAM. It performs marginally better in terms of speed, although not as much as I expected.&lt;/p&gt;

&lt;p&gt;I suspect there is a bottleneck somewhere when Gemma is loaded into memory, as the rest of the emulator runs fluidly. Perhaps an improvement somewhere that can be made.&lt;/p&gt;

&lt;h1&gt;
  
  
  Gemma 2b &amp;amp; Gemma 7b
&lt;/h1&gt;

&lt;p&gt;The Gemma models compatible with MediaPipe are the &lt;code&gt;gemma-2b&lt;/code&gt; versions. The &lt;code&gt;2b&lt;/code&gt; stands for 2 billion parameters. The amount of parameters working together to make the model work.&lt;/p&gt;

&lt;p&gt;These are the values set within the model during training to provide the connections and inferences between each other when you ask Gemma a question.&lt;/p&gt;

&lt;p&gt;There is also a &lt;code&gt;gemma-7b&lt;/code&gt; collection, which use 7 billion parameters. These are not supported by MediaPipe however. Maybe one day!&lt;/p&gt;

&lt;p&gt;If you’re interested in understanding more about parameters when it comes to LLMs I recommend this &lt;a href="https://gregoreite.com/ai-101-what-is-a-parameter-in-a-chatbot-llm/"&gt;page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Having a 2 billion parameter model being loaded and running on a mobile device is an impressive achievement. How well does it work though? Let’s find out.&lt;/p&gt;

&lt;h1&gt;
  
  
  gemma-2b-it-cpu-int4
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;gemma-2b-it-cpu-int4&lt;/code&gt; is a 4 bit LLM. This means each parameter used by the model has a memory size of 4 bits. The benefit here is the total size of the model is smaller, however the reduced memory size for each parameter means the accuracy and quality of the model are also affected.&lt;/p&gt;

&lt;p&gt;So how does &lt;code&gt;gemma-2b-it-cpu-int4&lt;/code&gt; perform? Not so great to be honest. Here are a few screenshots of my attempts to play Simon Says using the prompt above and asking it general questions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S0zggMe1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/gemma-2b-it-cpu-int4-conversations.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S0zggMe1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/gemma-2b-it-cpu-int4-conversations.png" alt="Gemma 2b it cpu int4 conversation 1" width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The responses were unexpected and it was frustrating to get the model to do anything resembling a game of Simon Says. It would veer off into a different topic and hallucinated inaccurate infomation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hallucinations&lt;/strong&gt; are a phoenema where LLMs speak falsehoods and untruthful things as if they are fact. Take the example above, it’s not true you can drive to Mars in 60 minutes at 60mph. Not yet anyway. 😃&lt;/p&gt;

&lt;p&gt;There was also a lack of context awareness. Meaning it couldn’t remember something I mentioned earlier in a conversation. This is likely due to the constrainted size of the model.&lt;/p&gt;

&lt;p&gt;After a while I gave up on this model and decided to try the larger 8 bit sized model.&lt;/p&gt;

&lt;h1&gt;
  
  
  gemma-2b-it-cpu-int8
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;gemma-2b-it-cpu-int8&lt;/code&gt; is an 8 bit LLM. Its larger in size to its 4 bit sibling. Meaning it can be more accurate and provide better quality answers. So what was the outcome here?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Oj63cp69--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/gemma-2b-it-cpu-int8-conversations.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Oj63cp69--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darrylbayliss.net/images/gemma-2b-it-cpu-int8-conversations.png" alt="Gemma 2b it cpu int8 conversations" width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This model was able to grasp the idea of Simon Says, immediately assuming the role of Simon. Unfortunately it too suffered from a lack of context awareness.&lt;/p&gt;

&lt;p&gt;To counter this I needed to reprompt the model everytime with the rules of Simon Says and combine it with another prompt to ask it to provide a task.&lt;/p&gt;

&lt;p&gt;The task prompts are randomly picked from a list to pass into Gemma, giving some variety in the tasks being asked.&lt;/p&gt;

&lt;p&gt;Here is an example of what is happening below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;SimonSaysPrompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
    You are a Simon in a game of Simon Says. Your objective is to ask the player to perform tasks.

    For every task you give, you must prefix it with the words "Simon says".

    You must not ask the player to do anything that is dangerous, unethical or unlawful.

    Do not try to communicate with the player. Only ask the player to perform tasks.
"""&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;MovePrompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimonSaysPrompt&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"""
    Give the player a task related to moving to a different position.
"""&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;SingASongPrompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimonSaysPrompt&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"""
    Ask the player to sing a song of their choice.
"""&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;TakePhotoPrompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimonSaysPrompt&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"""
    Give the player a task to take a photo of an object.
"""&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;prompts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;MovePrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;SingASongPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;TakePhotoPrompt&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llmInference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does ocassionally throw a curve ball response that seems out of character. I’m putting this down to the size of the model. It’s also worth considering this is only v1.&lt;/p&gt;

&lt;p&gt;Once the prompts were set in stone I found it was useful to rely on the prompts only and not take the user input into consideration. Because the model lacks context awareness the user input causes it to stop playing Simon Says and instead respond to the input.&lt;/p&gt;

&lt;p&gt;Adding this bit of trickery wasn’t a satisfying outcome, but one needed to keep Gemma playing Simon Says.&lt;/p&gt;

&lt;h1&gt;
  
  
  Impressions and Thoughts
&lt;/h1&gt;

&lt;p&gt;So can Gemma play Simon Says on an Android device? I’m going to say “kind of, with help”.&lt;/p&gt;

&lt;p&gt;I would like to see the 4 bit version of Gemma 2b responding more intuitively. Making Gemma 2b context aware to avoid the need to reprompt it for every request and being careful with user input would help too.&lt;/p&gt;

&lt;p&gt;For simple requests needing only a single prompt. I can see Gemma 2b being able to comfortably handle these tasks.&lt;/p&gt;

&lt;p&gt;Its also worth bearing in mind these are v1 of the models. The fact they run and work on a mobile operating system is an impressive achievement!&lt;/p&gt;

&lt;h1&gt;
  
  
  The Future of on Device LLMs
&lt;/h1&gt;

&lt;p&gt;What about the future of LLMs on mobile devices? There’s two barriers I see. Hardware limitations and practical use cases.&lt;/p&gt;

&lt;p&gt;I think we’re at a point where only high end devices can effectively run these models. Devices that come to mind are the Pixel 7 or Pixel 8 series of phones with their Tensor G chips, and Apples iPhone with their Neural Engine chip.&lt;/p&gt;

&lt;p&gt;We need to see these kind of specifications filtering through to mid-range phones.&lt;/p&gt;

&lt;p&gt;Interesting ideas could come from on device LLMs tapping into &lt;strong&gt;Retrieval Augmented Generation&lt;/strong&gt;. A technique for LLMs to communicate with external data sources to retrieve additional context when providing answers. This could be an effective way to boost performance.&lt;/p&gt;

&lt;p&gt;The second barrier is finding practical use cases. I think these are limited whilst devices can communicate with more powerful LLMs over the internet. GPT-4 from OpenAI for instance is rumoured to support over a trillion parameters!&lt;/p&gt;

&lt;p&gt;There could come a time though where the cost of deploying these models on mobile devices becomes cheaper than hosting them in the cloud. Since cost cutting is all the rage these days I can see this being a viable use case.&lt;/p&gt;

&lt;p&gt;There’s also the privacy benefits of having your own personal LLM, with no infomation leaving the confines of your device. A useful benefit that will appeal to privacy conscious app users.&lt;/p&gt;

&lt;p&gt;My bet is we’re still a few years away from LLMs being regularly deployed on device.&lt;/p&gt;

&lt;h1&gt;
  
  
  Useful Resources
&lt;/h1&gt;

&lt;p&gt;If you’re keen to try Gemma for yourself on a mobile device here are some resources to help:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemma&lt;/strong&gt; : The &lt;a href="https://ai.google.dev/gemma"&gt;offical Gemma website&lt;/a&gt; contains a wealth of infomation including benchmarks, quick start guides, and infomation on Googles approach to responsible Generative AI development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MediaPipe&lt;/strong&gt; : MediaPipe has its own &lt;a href="https://developers.google.com/mediapipe/solutions/guide"&gt;Google Developer section&lt;/a&gt; where you can learn more about it and how to use it. Highly recommended reading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Developer Group Discord&lt;/strong&gt; : The Google Developer Group &lt;a href="https://ai.google.dev/docs/discord"&gt;Discord&lt;/a&gt; has dedicated channels to Generative AI. Check out the #gemma, #gemini and #ml channels to chat with like minded people.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simons Says App:&lt;/strong&gt; Clone and run the &lt;a href="https://github.com/DarrylBayliss/Simon-Says-Android"&gt;sample code&lt;/a&gt; for this blog post to see it in action. It also includes usage of the Image Classification Task from MediaPipe. Setup instructions are in the README.&lt;/p&gt;

&lt;h1&gt;
  
  
  Footnotes
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Updated 23/03/24 to mention calling the LLM inference from an IO thread&lt;/strong&gt; &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It occurred to me after writing this post that calling out to gemma is a read / write operation on a file. Moving the &lt;code&gt;.generateResponse()&lt;/code&gt; method out to an IO thread will avoid the immense jank when gemma is loaded into memory:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;withContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;llmInference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>android</category>
      <category>mobile</category>
      <category>machinelearning</category>
      <category>ai</category>
    </item>
    <item>
      <title>Interacting with Apple Maps Through URL Parameters</title>
      <dc:creator>Darryl Bayliss</dc:creator>
      <pubDate>Sun, 18 Feb 2024 06:00:00 +0000</pubDate>
      <link>https://dev.to/darrylbayliss/interacting-with-apple-maps-through-url-parameters-1p07</link>
      <guid>https://dev.to/darrylbayliss/interacting-with-apple-maps-through-url-parameters-1p07</guid>
      <description>&lt;p&gt;I recently made a change to &lt;a href="https://www.darrylbayliss.net/building-ev-buddy/"&gt;EVBuddy&lt;/a&gt; to update what happens when a user needs directions via Apple Maps.&lt;/p&gt;

&lt;p&gt;From the outside it’s a simple action. The website takes the user to the Maps App.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1riU6XdP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://darrylbayliss.net/images/open-apple-maps.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1riU6XdP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://darrylbayliss.net/images/open-apple-maps.gif" alt="Directions via Apple Maps" width="600" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The interaction between EVBuddy and the Maps App on MacOS is possible because Maps provides a &lt;strong&gt;URL Scheme&lt;/strong&gt;. A way for Maps to understand an URL and show the correct content.&lt;/p&gt;

&lt;p&gt;If you opened a web link before and been directed to an App, you’ve used the App’s URL Scheme!&lt;/p&gt;

&lt;h1&gt;
  
  
  The Apple Maps URL Scheme
&lt;/h1&gt;

&lt;p&gt;So how does the Maps URL Scheme work? Previously, I used the following URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appleMapsUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://maps.apple.com/?q=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part of the URL here is &lt;code&gt;q=&lt;/code&gt;. This treats what comes after &lt;code&gt;=&lt;/code&gt; as a query, meaning Maps acts like a search engine.&lt;/p&gt;

&lt;p&gt;Type in &lt;code&gt;Cinemas&lt;/code&gt;, &lt;code&gt;Beach&lt;/code&gt; or &lt;code&gt;Italian Restaurants&lt;/code&gt; and Maps will open and search for a matching result.&lt;/p&gt;

&lt;p&gt;You can try this for yourself if you use an iOS, iPadOS or MacOS device with Maps installed. Type &lt;code&gt;http://maps.apple.com/?q=&lt;/code&gt; into your browser and after &lt;code&gt;=&lt;/code&gt; search for something.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HeWEaimK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://darrylbayliss.net/images/apple-maps-query-search.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HeWEaimK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://darrylbayliss.net/images/apple-maps-query-search.gif" alt="Query Search via Apple Maps" width="600" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Adding a Destination
&lt;/h1&gt;

&lt;p&gt;For EVBuddy, I don’t want to search, I want to provide a destination for people to drive to and charge their car. How can we do that?&lt;/p&gt;

&lt;p&gt;Fortunately, Apple provide a page with all the accepted parameters to use with Maps. You can find the page &lt;a href="https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Looking at the page, Maps supports a parameter called &lt;code&gt;daddr&lt;/code&gt;. This stands for Destination Address, which we can pass the Postcode of the charging point as our destination.&lt;/p&gt;

&lt;p&gt;Our URL now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appleMapsUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://maps.apple.com/?daddr=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Choosing the Transport Type
&lt;/h1&gt;

&lt;p&gt;Next, it would be good if the directions are calculated by car. Maps gives different directions depending on the type of transport.&lt;/p&gt;

&lt;p&gt;To do that, there’s another parameter called &lt;code&gt;dirflg&lt;/code&gt; we can use. The value of a car for this parameter is &lt;code&gt;d&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you have an idea what this parameter name means let me know! My best guess is it was used for something else before being changed to its current purpose. 😃&lt;/p&gt;

&lt;p&gt;Adding this parameter to the URL, we can see it building up actions as we ask Maps to find directions for a car to the destination address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appleMapsUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://maps.apple.com/?dirflg=d&amp;amp;daddr=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Selecting the Map Type
&lt;/h1&gt;

&lt;p&gt;Finally, it would be good if a certain type of map is shown. Maps can show different types of map views depending on the users preference.&lt;/p&gt;

&lt;p&gt;In our case we want to show the standard map view so directions are presented clearly.&lt;/p&gt;

&lt;p&gt;Looking at the Maps URL parameters page, there’s a parameter called &lt;code&gt;t&lt;/code&gt; (maybe it stands for type?) we can use to set the map type. The value to show the standard map is &lt;code&gt;m&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Adding this parameter, we see the completed URL asking Maps to show the standard map and find directions for a car to the destination address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Show Apple Maps using the standard view (t=m), &lt;/span&gt;
&lt;span class="c1"&gt;// Using car as the mode of transport (dirflg=d) &lt;/span&gt;
&lt;span class="c1"&gt;// And appending a destination address (daddr=)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appleMapsUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://maps.apple.com/?t=m&amp;amp;dirflg=d&amp;amp;daddr=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the nice things about Maps is we don’t need to provide a starting address. Maps automatically uses the device location if it has perrmission.&lt;/p&gt;

&lt;h1&gt;
  
  
  Further Reading
&lt;/h1&gt;

&lt;p&gt;The URL scheme for Apple Maps gives developers (and users!) alot of flexibility to link out from other apps.&lt;/p&gt;

&lt;p&gt;If you have a specific usecase for yourself, check out the &lt;a href="https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html"&gt;URL Parameters&lt;/a&gt; documentation Apple provides and see what you can do.&lt;/p&gt;

&lt;p&gt;If you’re using Google Maps, Google provide similar functionality to link to Google Maps using their URL Scheme.&lt;/p&gt;

&lt;p&gt;You can find the Google documentation with all the supported parameters &lt;a href="https://developers.google.com/maps/documentation/urls/get-started"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>macos</category>
      <category>frontend</category>
      <category>react</category>
    </item>
    <item>
      <title>Building EV Buddy</title>
      <dc:creator>Darryl Bayliss</dc:creator>
      <pubDate>Mon, 18 Sep 2023 16:54:16 +0000</pubDate>
      <link>https://dev.to/darrylbayliss/building-ev-buddy-5hbj</link>
      <guid>https://dev.to/darrylbayliss/building-ev-buddy-5hbj</guid>
      <description>&lt;p&gt;In the past couple of years I've become interested in the cloud. I've spent a good chunk of my career working in mobile development, and if theres one trend I don't think is going away soon is apps powered by a platform. As any good engineer would do, I was keen to learn more.&lt;/p&gt;

&lt;p&gt;To start, I put myself through AWS certifications. I received AWS Cloud Practitioner Certification last year (passed first time!) and earlier this year passed my Associate Solutions Architect Certification (passed on the 2nd attempt after a washout of a 1st attempt). &lt;/p&gt;

&lt;p&gt;Official pieces of paper saying you can do things get a bad rep, I've met a few people who consider them worthless. I can understand this way of thinking as alot of these people have years of hard earned experience. &lt;/p&gt;

&lt;p&gt;I can also understand why employers and engineers like them. To me, they're an extra signal to employers and organisations that you have experience in the areas you claim to in your CV and done the hard work to prove it.&lt;/p&gt;

&lt;p&gt;So certifications are nice, but I also wanted a project to help showcase that knowledge. As part of my learning, I decided to build a full stack application. I'm happy to say v0.0.1 is ready and live, and it's called &lt;a href="https://evbuddy.io"&gt;EV Buddy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K0Cjd4GV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/55zokh9wxax3t0yfecx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K0Cjd4GV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/55zokh9wxax3t0yfecx3.png" alt="Hello EV Buddy!" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  EV Buddy
&lt;/h1&gt;

&lt;p&gt;EV Buddy is an interactive map showing publicly available electric vehicle charging points. You search for charging points using a postcode, and the map displays public charging points nearby. Users can then click on the charging point to see more infomation. For v0.0.1, EV Buddy is limited to searches in the UK.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uoFILhu1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/crz8y6ep7qjlkk6s0v06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uoFILhu1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/crz8y6ep7qjlkk6s0v06.png" alt="Shown Chargers" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each charging point provides infomation such as what charging connectors the point supports, who controls the charger, whether payment is required for usage, and the address of where it is located. Useful things to know if you're planning a journey in your EV.&lt;/p&gt;

&lt;p&gt;EV Buddy links directly to other mapping platforms. If you'd like to use Google Maps or Apple Maps to get directions to that charger, it's only a click of a button away.&lt;/p&gt;

&lt;p&gt;The data for the UK is requested from the &lt;a href="https://www.gov.uk/guidance/find-and-use-data-on-public-electric-vehicle-chargepoints"&gt;National Charging Registry&lt;/a&gt;, a database of public charging points available across the UK. It's free to access and regularly updated, meaning the map also benefits from these updates.&lt;/p&gt;

&lt;p&gt;Let's take a look at the stack.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Technical Stack
&lt;/h1&gt;

&lt;p&gt;EV Buddy uses the following technologies:&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;ReactJS (Frontend Development)&lt;/li&gt;
&lt;li&gt;Mapbox GL JS (Mapping Provider)&lt;/li&gt;
&lt;li&gt;MUI (Material Design UI Library for React)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Node.js / Express (Backend Development)&lt;/li&gt;
&lt;li&gt;Serverless Framework (Packages code into deployable AWS Lambda functions)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cloud Infrastructure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AWS S3 (Object Storage)&lt;/li&gt;
&lt;li&gt;AWS Lambda (Serverless computing)&lt;/li&gt;
&lt;li&gt;AWS Cloudfront (Content Distribution Network for Website)&lt;/li&gt;
&lt;li&gt;AWS Route 53 (Domain Registration / DNS Record Management)&lt;/li&gt;
&lt;li&gt;AWS Certificate Manager (Website / Endpoint Encryption)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is the cloud architectural diagram of how EV Buddy is put together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z37VPzPH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxfw8rvvi1qypdrlhctp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z37VPzPH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxfw8rvvi1qypdrlhctp.png" alt="EV Buddy AWS Architectural Diagram" width="800" height="989"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although there was alot of trial and error involved to get it right, the architecture diagram is not very complicated. Below, I've wrote up the more interesting challenges faced, the decisions made for the architecture, and the outcomes from that.&lt;/p&gt;

&lt;h1&gt;
  
  
  Choosing a Web Framework
&lt;/h1&gt;

&lt;p&gt;The first decision was the choice of tools for frontend development. ReactJS felt a natural choice as alot of the concepts behind Declarative UI frameworks on mobile find their roots within React. It was also a familar tool, as I'd completed a &lt;a href="https://www.freecodecamp.org/certification/fccb890088c-8989-4c20-910c-9caf5d13e101/back-end-development-and-apis"&gt;FreeCodeCamp backend course&lt;/a&gt; that used React as it's Frontend framework of choice.&lt;/p&gt;

&lt;p&gt;I went with using &lt;a href="https://create-react-app.dev/"&gt;Create React App&lt;/a&gt; to setup the initial build. There was little thought here except I wanted to get started quickly and Create React App allows you to do just that.&lt;/p&gt;

&lt;p&gt;Now I have more knowledge of the ecosystem I may of considered using &lt;a href="https://www.gatsbyjs.com/"&gt;Gatsby&lt;/a&gt; or &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;. My understanding is these frameworks provide more features like mobile support and automatic optimisations than the barebones setup you get with Create React App.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deciding on a Mapping Provider
&lt;/h1&gt;

&lt;p&gt;The next step was to choose a mapping provider. I initially tried Apple Maps Web API, unfortunately they only provide a Javascript based API. Something I wanted to avoid writing too much of within a React App.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://github.com/tonyduanesmith/react-apple-mapkitjs"&gt;attempts by people&lt;/a&gt; to wrap Apple Maps within React Components. For one reason or another I found these attempts to be lacking for what I needed.&lt;/p&gt;

&lt;p&gt;My next attempt was with Google Maps. This was a similar situation to Apple Maps, very little ReactJS support and a few brave developers trying their hand themselves to &lt;a href="https://www.npmjs.com/package/google-maps-react"&gt;wrap Google Maps&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;After getting setup with an API key and trying out some of these frameworks I found myself running into issues again. The map wouldn't load, there were graphical glitches. Enough trouble for me to decide persuing this further wasn't worthwhile and decide to move on.&lt;/p&gt;

&lt;p&gt;The third attempt was to use &lt;a href="https://docs.mapbox.com/mapbox-gl-js/guides/"&gt;Mapbox GL&lt;/a&gt;. This time I was making progress, I quickly had a map rendering and could move it about with little trouble. The API is again Javascript based but the integration and support from Mapbox to use their API with React made it easy to work with.&lt;/p&gt;

&lt;p&gt;There are rough edges to the API that make Mapbox feel lacking. For instance, there's no click method available for an individual marker. There's an &lt;a href="https://github.com/mapbox/mapbox-gl-js/issues/7793"&gt;issue raised&lt;/a&gt; dating from 2019 about it, with people suggesting various workarounds. &lt;/p&gt;

&lt;p&gt;The workaround I picked worked, but seems like a poor developer experience to have to rely on a workaround to detect a marker click.&lt;/p&gt;

&lt;p&gt;Despite these faults, I'm finding my use of Mapbox GL to be a good decision.&lt;/p&gt;

&lt;h1&gt;
  
  
  Node.js, Serverless and AWS Lambda
&lt;/h1&gt;

&lt;p&gt;My next decision was to decide on the tools to use for backend development. My &lt;a href="https://www.freecodecamp.org/certification/fccb890088c-8989-4c20-910c-9caf5d13e101/back-end-development-and-apis"&gt;FreeCodeCamp&lt;/a&gt; course had used Node.js for the backend, so I decided to rely on that experience to code a small server. The purpose of it to calling out to the National Charging Registry API and return the response to the website.&lt;/p&gt;

&lt;p&gt;This was an easy exercise. The difficult part came when deploying the server to AWS and balancing costs. I didn't want the hassle of having to manage a server, so initially went with AWS Elastic Beanstalk.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/elasticbeanstalk/"&gt;Elastic Beanstalk&lt;/a&gt; is a service taking the pain out of managing an environment and does all the hard work for you. You give it the code, and it gets to work deploying everything you need for your environment. It's an impressive product.&lt;/p&gt;

&lt;p&gt;The downside of Elastic Beanstalk is it's expensive, even for tiny workloads and deploying iterations. I barely did anything and managed to rack up an AWS bill of about ~$80 in 1 month. No where sustainable in my eyes for a small project.&lt;/p&gt;

&lt;p&gt;I decided to see if Lambda functions were cheaper on AWS and could still work with my Node.js server. To my surprise AWS offers a free tier for Lambda functions, allowing &lt;strong&gt;1 Million&lt;/strong&gt; free requests per month.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UpeTO3Fz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/llrkthjqh1rxg09o5o5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UpeTO3Fz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/llrkthjqh1rxg09o5o5g.png" alt="1 Million Free Lambda Requests" width="800" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was progress, however I had built a server, not a Lambda function. A quick internet search solved this problem though, as I found a framework called &lt;a href="https://www.serverless.com/"&gt;Serverless&lt;/a&gt;. Serverless provides a wrapping for Node.js, turning it into a deployable Lambda function. &lt;/p&gt;

&lt;p&gt;Serverless also provides tooling to automate deployment of the function via config files, including setting a custom domain and securing the domain with a SSL certificate.&lt;/p&gt;

&lt;p&gt;With this I found a solution that is essentially free to run and gives the flexibility to deploy the lambda separately to the website. Deploying via Elastic Beanstalk on the otherhand, meant the website and server were tightly coupled.&lt;/p&gt;

&lt;h1&gt;
  
  
  Route 53
&lt;/h1&gt;

&lt;p&gt;Once the website and Lambda were in a good state I needed to decide upon a domain name and what DNS registar to use. Fortunately AWS provide their own, called Route 53. Route 53 provides most of the features you would expect from a DNS Registar, ranging from Domain purchase to Adding Records for your domain.&lt;/p&gt;

&lt;p&gt;Route 53 also provide additional features like &lt;a href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html"&gt;DNS routing policies&lt;/a&gt;. These route traffic depending on conditions such geographical location, weighted distribution between destinations and even latency to ensure the request provides the quickest response.&lt;/p&gt;

&lt;p&gt;Route 53 also uses the concept of a &lt;strong&gt;Hosted Zone&lt;/strong&gt;, a place where DNS records are stored for your domain. Useful if you have multiple domains.&lt;/p&gt;

&lt;p&gt;One of the things I found with hosted zones is the NS records change if you delete and recreate a hosted zone. This can be a problem if you rely on the NS records for operations like DNS validation for an SSL certificate. The related NS records never change for a domain name, but do for a hosted zone.&lt;/p&gt;

&lt;p&gt;I figured this out the hard way and only realised the issue after about 6-8 hours of investigation. A mistake I'll aim not to make again!&lt;/p&gt;

&lt;p&gt;Once my hosted zone was setup with my domain, I was now ready to use it for my application.&lt;/p&gt;

&lt;h1&gt;
  
  
  AWS Certificate Manager
&lt;/h1&gt;

&lt;p&gt;I wanted to make sure the website and Lambda function were accessible using HTTPS. To do that I needed a certificate, and to get one I had to use a service called &lt;a href="https://aws.amazon.com/certificate-manager/"&gt;AWS Certificate Manager&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AWS Certificate Manager provides certificates for web domains, including subdomains. Before AWS issues the certificate it requires you to verify yourself as the owner of the domain.&lt;/p&gt;

&lt;p&gt;This is easy to do if you can use &lt;strong&gt;DNS Validation&lt;/strong&gt;, a way of allowing AWS to use DNS to verify you as the domain owner using NS records. These records are already setup if you use Route 53, or you can add them to your own DNS Registry.&lt;/p&gt;

&lt;p&gt;As mentioned in the previous section, I had deleted and recreated the hosted zone created for evbuddy.io, which meant the NS records AWS were looking for didn't match.&lt;/p&gt;

&lt;p&gt;Once I fixed this however the validation happened in a couple of minutes and I had a certificate ready to use.&lt;/p&gt;

&lt;p&gt;Setting up the stack to use the certificate was a few clicks in the AWS console, or &lt;strong&gt;Click-Ops&lt;/strong&gt; if you're familar with the term. 😉&lt;/p&gt;

&lt;h1&gt;
  
  
  S3 &amp;amp; Cloudfront
&lt;/h1&gt;

&lt;p&gt;With a domain and certifcate ready to use, my next step was to store my website on AWS and provide access via my domain. The simplest way to do this was to create an S3 bucket and use it as the origin for a Cloudfront distribution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/s3/"&gt;S3&lt;/a&gt; is an object based storage product. It can store files in various formats and provides configuration on where these files can be accessed and how long it takes to access them.&lt;/p&gt;

&lt;p&gt;The configuration of S3 determines the pricing paid. By default new users of AWS get 12 months free of standard S3 storage with 20,000 GET requests and 2,000 PUT requests. &lt;/p&gt;

&lt;p&gt;Outside of that AWS gives generous transfer rates in and out of S3. Transfer rates in are free of charge and transfer rates out are free for the first 100GB per month.&lt;/p&gt;

&lt;p&gt;For EV Buddy, I created two S3 buckets. The first one contains the files for the website, whilst the second bucket points to the 1st bucket. The reason for this is both buckets are intended to be used as content points for AWS Cloudfront. Each responds to different URL requests a user may make.&lt;/p&gt;

&lt;p&gt;The idea is it makes rerouting requests the user makes to the same website in varying ways simpler. A user querying &lt;a href="//evbuddy.io"&gt;evbuddy.io&lt;/a&gt; can be treated differently to &lt;a href="//www.evbuddy.io"&gt;www.evbuddy.io&lt;/a&gt; for instance.&lt;/p&gt;

&lt;p&gt;With the buckets setup, it was time to turn attention to provide access to the website stored within S3. This is done is via AWS Cloudfront.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/cloudfront/"&gt;Cloudfront&lt;/a&gt; is AWS's content distribution network product. It provides a way for developers to distribute their applications, APIs and content across the globe with low latency, using the infrastructure built by AWS. It's designed to be highly available, secure and scalable for your needs.&lt;/p&gt;

&lt;p&gt;Configuring cloudfront is simple. You tell it where to fetch the content it needs to deal with, let it know relevant infomation like the domain name to respond, what certificate to use for HTTPS, what the caching policy is and you're done!&lt;/p&gt;

&lt;p&gt;Similar to the S3 bucket setup, EV Buddy uses two Cloudfront distributions. Each one handling a different domain request. Both distributions eventually route through to the same S3 Bucket.&lt;/p&gt;

&lt;p&gt;One snag I noticed is Cloudfront's aggressive approach to Caching. If I push a change to the website, it wouldn't reflect instantly. This isn't neccessarily a bad thing, as Cloudfront is doing what it's supposed to. It just means I had to be mindful to immediately invalidate the cache once a change has been pushed.&lt;/p&gt;

&lt;p&gt;To solve this, I set object metadata on the files stored in S3. This metadata allowed me to set a &lt;code&gt;Cache-Control&lt;/code&gt; value for each file, Cloudfront respects this value by checking it before delivering content. &lt;/p&gt;

&lt;p&gt;If the value is older than what Cloudfront expects, Cloudfront updates the content from S3 and passes that through to the requester. Magic!&lt;/p&gt;

&lt;h1&gt;
  
  
  Costs
&lt;/h1&gt;

&lt;p&gt;2023 has been a hard year for most companies and cost cutting is high on the agenda. I thought it was good to keep this in mind and see how well I can optimise costs. Here is a brief look into the costs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3x3KfnpQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ssw1ra08y9zyi1ewhk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3x3KfnpQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ssw1ra08y9zyi1ewhk3.png" alt="EV Buddy Costs" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The peak at the left of the graph shows the costs of experimenting with Elastic Beanstalk. These were raising into nearly $80 per month territory, which as mentioned earlier was way beyond my budget.&lt;/p&gt;

&lt;p&gt;With the restructuring to use AWS Lambda and relying on free tier pricing for AWS Lambda, S3, and AWS Cloudfront I managed to slash these costs to $1.66 per month as of writing. I expect an element of variance in these costs depending on traffic, but I'm putting a comfortable estimate it won't raise to more than $5 per month.&lt;/p&gt;

&lt;p&gt;One outlier cost not shown is $71 for the evbuddy.io domain name. I expect this to be an annual cost for as long as the website is available. .io domain names aren't cheap, but they do add some unique flair to a website URL. :)&lt;/p&gt;

&lt;p&gt;In total that translates to a cost of $90.92 per year to run evbuddy.io. The worse case scenario in my mind if costs unexpectedly rise would be $130.00 per year.&lt;/p&gt;

&lt;p&gt;Not bad for a website hosted within S3, distributed across the globe and communicating with an external API.&lt;/p&gt;

&lt;h1&gt;
  
  
  Improvements
&lt;/h1&gt;

&lt;p&gt;Below is a list of improvements I would like to make to EV Buddy in the future.&lt;/p&gt;

&lt;p&gt;More countries: EV Buddy so far only supports the UK. I'd like to add support for more countries.&lt;/p&gt;

&lt;p&gt;Better Mobile Support: The website is usable on mobile, but has usability problems. The map can get stuck when closing the detail page and the toolbar is minimal.&lt;/p&gt;

&lt;p&gt;Improved Connector Detail Page: I'd like to clean up the connector detail page. It is basic and unless you're a EV enthusiast may not be very helpful.&lt;/p&gt;

&lt;p&gt;Filtering: I'd like to include filters to the map for users to find chargers based on their preferences. Filters could include charging output, charging networks, or connector types.&lt;/p&gt;

&lt;p&gt;Move to TypeScript: The frontend is written entirely in Javascript, but I would like to migrate it to Typescript for the type safety it provides.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing Thoughts
&lt;/h1&gt;

&lt;p&gt;Building this project has helped to cement alot of my learning from my AWS certifications. I have enough knowledge to feel confident I can build and help scale a platform whilst keeping costs down.&lt;/p&gt;

&lt;p&gt;It's also helped improve my knowledge of Frontend development, particularly in React. I'd encourage anyone wanting to development their skills in cloud development to come up with their own project and get building. I'm incredibly proud of EV Buddy and will continue to tinker with it as I please.&lt;/p&gt;

&lt;p&gt;You can try out EV Buddy at &lt;a href="https://evbuddy.io"&gt;https://evbuddy.io/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>mapping</category>
      <category>cloud</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Jetpack Compose for Maps</title>
      <dc:creator>Darryl Bayliss</dc:creator>
      <pubDate>Wed, 09 Aug 2023 19:00:48 +0000</pubDate>
      <link>https://dev.to/darrylbayliss/jetpack-compose-for-maps-4ip2</link>
      <guid>https://dev.to/darrylbayliss/jetpack-compose-for-maps-4ip2</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is based off a talk given at Google I/O Extended for Google Developers Group London in July 2023. The slides for the talk are available &lt;a href="https://www.canva.com/design/DAFoErhq1aQ/fsinMlWcLcByNQdudCIvEA/edit?utm_content=DAFoErhq1aQ&amp;amp;" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s hard to imagine Jetpack Compose 1.0 was released in &lt;a href="https://android-developers.googleblog.com/2021/07/jetpack-compose-announcement.html" rel="noopener noreferrer"&gt;July 2021&lt;/a&gt;. Fast forward two years and with &lt;a href="https://android-developers.googleblog.com/2023/05/whats-new-in-jetpack-compose.html" rel="noopener noreferrer"&gt;24% of the top 1000 apps on Google Play adopting Compose&lt;/a&gt; it’s easy to understand why.&lt;/p&gt;

&lt;p&gt;Amongst all the excitement, one corner of Modern Android Development I feel recieves little attention is Google Maps. It’s been a while since I used the SDK, so was pleasantly surprised to see Google Maps was catching up with the times and released their own &lt;a href="https://cloud.google.com/blog/products/maps-platform/compose-maps-sdk-android-now-available" rel="noopener noreferrer"&gt;Compose library&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This will be welcome news to companies &amp;amp; engineers working in the mapping space. Mobile mapping is a $35.5B industry, with predictions of it raising to $87.7B by 2028. An Compound Annual Growth Rate (CAGR) of 19.83%. &lt;a href="https://www.mordorintelligence.com/industry-reports/mobile-mapping-system-market-industry" rel="noopener noreferrer"&gt;&lt;em&gt;Source&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqowv2cdzwqlht47uwcyn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqowv2cdzwqlht47uwcyn.png" alt="Market Growth of Mobile Mapping between 2023-2028"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why is this important? A bigger market means more opportunities for companies to derive revenue from applications of mobile mapping. These range from the usual use cases, food, grocery delivery and ride hailing services. If you dig deep however, there are applications that aren’t immediately obvious. Below are the examples I could find after a brief search.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzzi2yi6od5a34mljbwp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzzi2yi6od5a34mljbwp.png" alt="Word map of sectors using mobile mapping"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mobile maps are great for Smart Cities, helping to manage the heartbeat of a city and visualising data in a way to better understand and react to its challenges. Useful for city planners, emergency response organisations or everyday residents.&lt;/p&gt;

&lt;p&gt;Resource management also benefits from mapping solutions. Ranging from agriculture to fishing, mining to forestry, maps provide those in this line of work a perspective to make the right decisions to harvest materials in a sustainable way.&lt;/p&gt;

&lt;p&gt;Transportation relies heavily on mapping technology. Not just consumer apps like Google Maps or Uber, but business level functions like understanding what a businesses fleet of vehicles are located. Transportation agencies also use maps to manage traffic and help make decisions on where to direct traffic to ease the flow.&lt;/p&gt;

&lt;p&gt;Finally, with climate change and the weather being increasingly unpredictable, maps allows meteological agencies, emergency response units, and wildlife conservationists to understand how our world is changing and what we can do to take positive steps to reduce this.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources&lt;/em&gt;: &lt;a href="https://www.mordorintelligence.com/industry-reports/mobile-mapping-system-market-industry" rel="noopener noreferrer"&gt;Mordor Intelligence&lt;/a&gt;, &lt;a href="https://www.gminsights.com/industry-analysis/mobile-mapping-market" rel="noopener noreferrer"&gt;GMInsights&lt;/a&gt;, &lt;a href="https://www.alliedmarketresearch.com/mobile-mapping-market-A17381" rel="noopener noreferrer"&gt;Allied Market Research&lt;/a&gt;, &lt;a href="https://www.expertmarketresearch.com/reports/mobile-mapping-market" rel="noopener noreferrer"&gt;EMR Research&lt;/a&gt;, &lt;a href="https://www.google.com/earth/outreach/special-projects/air-quality/" rel="noopener noreferrer"&gt;Google Earth Outreach&lt;/a&gt;, &lt;a href="https://www.researchandmarkets.com/reports/5184622/gis-in-disaster-management-market-research" rel="noopener noreferrer"&gt;Research &amp;amp; Markets&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the world providing more and more data, it’s a good time to learn how to put that data on a map. Let’s do that and get back to the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using Google Maps for Compose&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google Maps for Compose relies on the following dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"com.google.maps.android:maps-compose:2.11.4"&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"com.google.android.gms:play-services-maps:18.1.0"&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional Util Library&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"com.google.maps.android:maps-compose-utils:2.11.4"&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'com.google.maps.android:maps-compose-widgets:2.11.4'&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional Accompanist permissions to request permissions in compose&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s2"&gt;"com.google.accompanist:accompanist-permissions:0.31.5-beta"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google Maps for Compose is built on top of the Google Maps SDK, so you need to import the Compose library and the maps SDK. You won’t need to use most objects in the Google Maps SDK, as the compose library wraps most of these in Composables.&lt;/p&gt;

&lt;p&gt;The utils and widgets libraries are an optional dependency. The utils library provides the ability to cluster markers on the maps, whilst widgets provides additional UI components. You’ll see these in use later.&lt;/p&gt;

&lt;p&gt;For this post, I’ve included the request permissions library from Accompanist to demonstrate how to request location permissions, an often used permission with maps. Accompanist is an experimental library for Google to try out and gather feedback for features not yet part of Jetpack Compose.&lt;/p&gt;

&lt;p&gt;Finally, you need to go to the &lt;a href="https://console.cloud.google.com/projectselector2/google/maps-apis/credentials" rel="noopener noreferrer"&gt;Google Developer Console&lt;/a&gt;, sign up for a Google Maps SDK API key, and add it to your project. There’s a guide on the &lt;a href="https://developers.google.com/maps/documentation/android-sdk/get-api-key#creating-api-keys" rel="noopener noreferrer"&gt;Google Maps Developer Docs&lt;/a&gt; on how to do this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Tip:&lt;/strong&gt; In the Google Developer Console lock down your API key so it only works with your application. This is avoids any unauthorised use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Showing a Map&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Showing a map is as simple as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt; &lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hydePark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.508610&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.163611&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberCameraPositionState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CameraPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromLatLngZoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hydePark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;GoogleMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cameraPositionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Marker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MarkerState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hydePark&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hyde Park"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;snippet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Marker in Hyde Park"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;LatLng&lt;/code&gt; object with the position of an area, and use it in conjunction with &lt;code&gt;rememberCameraPositionState&lt;/code&gt; to set the initial position of the camera. This method remembers the position of the map as you move about using your hands or programmatically. Without this method Compose would recalulate the map back to its initial position on every state change.&lt;/p&gt;

&lt;p&gt;Next, create a &lt;code&gt;GoogleMap&lt;/code&gt; compose and pass in a modifier of your choice and the camera state. &lt;code&gt;GoogleMap&lt;/code&gt; also provides a Slot API to pass in additional composables, these composables are what you want to draw on the map.&lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;Marker&lt;/code&gt; composable, then add a &lt;code&gt;MarkerState&lt;/code&gt; containing the position of the marker inside. Finally, add a title and description of the marker.&lt;/p&gt;

&lt;p&gt;Running this gives a nice aerial view of West London with a marker in Hyde Park.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi70jcwpvcuxkedjovi3v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi70jcwpvcuxkedjovi3v.png" alt="Marker in Hyde Park"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customising the Marker Window&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can customise the window of the marker by using a &lt;code&gt;MarkerInfoWindowContent&lt;/code&gt; Composable. This also has a slot based API, meaning you can pass in your own composables to render your custom UI in the window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hydePark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.508610&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.163611&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberCameraPositionState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CameraPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromLatLngZoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hydePark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;GoogleMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cameraPositionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;MarkerInfoWindowContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MarkerState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hydePark&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hyde Park"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;snippet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Marker in Hyde Park"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;horizontalAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterHorizontally&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;fontWeight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FontWeight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bold&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hyde Park is a Grade I-listed parked in Westminster"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
                            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;border&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="nc"&gt;BorderStroke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Gray&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                                &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RectangleShape&lt;/span&gt;
                            &lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="n"&gt;painter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;painterResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hyde_park&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"A picture of hyde park"&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this shows the custom window above the marker when you tap on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjovvl8sf3imttheivtm1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjovvl8sf3imttheivtm1.png" alt="Marker with custom window"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Showing multiple markers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Showing multiple markers is as simple as adding as many as you need. Let’s add markers for a few different parks in West London.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt; &lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hydePark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.508610&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.163611&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;regentsPark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.531143&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.159893&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;primroseHill&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.539556&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.16076088&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberCameraPositionState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CameraPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromLatLngZoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hydePark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;GoogleMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cameraPositionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Marker 1&lt;/span&gt;
            &lt;span class="nc"&gt;Marker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MarkerState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hydePark&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hyde Park"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;snippet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Marker in Hyde Park"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// Marker 2&lt;/span&gt;
            &lt;span class="nc"&gt;Marker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MarkerState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regentsPark&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Regents Park"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;snippet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Marker in Regents Park"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// Marker 3&lt;/span&gt;
            &lt;span class="nc"&gt;Marker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MarkerState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;primroseHill&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Primrose Hill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;snippet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Marker in Primrose Hill"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the code and you will see your markers appear on the map.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbeqblllkjzlbq6qijbmr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbeqblllkjzlbq6qijbmr.png" alt="Multiple Park Markers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clustering Markers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A map can get busy within a short amount of time. If you’re trying to show 300 markers, it’s going to be visually hard for a user to understand what is going on. Google Maps and your device won’t thank you ether, since it will have to render every single marker, impacting performance and battery life.&lt;/p&gt;

&lt;p&gt;The solution to this is &lt;strong&gt;Clustering&lt;/strong&gt;, a technique grouping markers close to each other into a single marker. This Clustering happens on a zoom level basis. As you zoom the map out the markers will group together into a cluster, as you zoom in the cluster will split into individual markers.&lt;/p&gt;

&lt;p&gt;Google Maps for Compose provides this out of the box via a &lt;code&gt;Clustering&lt;/code&gt; composable. There’s no need to write complex sorting or filtering for the clustering to occur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt; &lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hydePark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.508610&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.163611&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;regentsPark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.531143&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.159893&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;primroseHill&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.539556&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.16076088&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;crystalPalacePark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.42153&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.05749&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;greenwichPark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.476688&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.000130&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;lloydPark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;51.364188&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.080703&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberCameraPositionState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CameraPosition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromLatLngZoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hydePark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;GoogleMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cameraPositionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;parkMarkers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;mutableStateListOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nc"&gt;ParkItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hydepark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hyde Park"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Marker in hyde Park"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nc"&gt;ParkItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regentspark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Regents Park"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Marker in Regents Park"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nc"&gt;ParkItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primroseHill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Primrose Hill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Marker in Primrose Hill"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nc"&gt;ParkItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crystalPalacePark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Crystal Palace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Marker in Crystal Palace"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nc"&gt;ParkItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;greenwichPark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Greenwich Park"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Marker in Greenwich Park"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nc"&gt;ParkItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lloydPark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Lloyd park"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Marker in Lloyd Park"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nc"&gt;Clustering&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parkMarkers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;onClusterClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle when the cluster is tapped&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;onClusterItemClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle when a marker in the cluster is tapped&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;ParkItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;itemPosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;itemTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;itemSnippet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClusterItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getPosition&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;LatLng&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;itemPosition&lt;/span&gt;

        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getTitle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;itemTitle&lt;/span&gt;

        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getSnippet&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;itemSnippet&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;ParkItem&lt;/code&gt; data class added. We need this because items passed into a &lt;code&gt;Clustering&lt;/code&gt; composable have to conform to the &lt;code&gt;ClusterItem&lt;/code&gt; interface. The interface provides the Cluster with the position, title and snippet for each marker.&lt;/p&gt;

&lt;p&gt;Zoom in and out, and you’ll see the clustering in action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k8socjkbx9aq6t57vuu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k8socjkbx9aq6t57vuu.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting Location Permission&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Maps and user position often go hand in hand, so it makes sense for some mapping apps to ask permission for the users location.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat a users permission with respect&lt;/strong&gt; if you do this, location permission is one of the most sensitive permissions to acquire from a user. Make sure you inform the user of why you need this permission and actively show the benefits of granting it. Bonus points if your app partially functions without the need for permission at all.&lt;/p&gt;

&lt;p&gt;Google provide some great guides on how to handle &lt;a href="https://developer.android.com/training/location/request-updates" rel="noopener noreferrer"&gt;users location&lt;/a&gt;, as well as a separate guide for &lt;a href="https://developer.android.com/training/location/background" rel="noopener noreferrer"&gt;accessing location data in the background&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So you’ve done your due diligence and decided you do need the users permission to access location. With the permissions library in Accompanist you do it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Don't forget to add the permissions to AndroidManifest.xml&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;allLocationPermissionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberMultiplePermissionsState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACCESS_COARSE_LOCATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACCESS_FINE_LOCATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Check if we have location permissions&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;allLocationPermissionsState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allPermissionsGranted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Show a component to request permission from the user&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;horizontalAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterHorizontally&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;verticalArrangement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Arrangement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RoundedCornerShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;textAlign&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextAlign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"This app functions 150% times better with percise location enabled"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;allLocationPermissionsState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launchMultiplePermissionsRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Grant Permission"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Via accompanist we’re checking to see if the app has access to the &lt;code&gt;ACCESS_FINE_LOCATION&lt;/code&gt; permission, or a high level of GPS accuracy in english. It’s important to include the requested permissions in the Android manifest, as you won’t be able to request the permissions otherwise. The Android system and Google Play store also use the manifest to understand how your app works and inform users.&lt;/p&gt;

&lt;p&gt;If permission isn’t granted, a small dialog composable is shown explaining the need for the permission and a button to launch the permission request via the system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.darrylbayliss.net%2Fimages%2Fgoogle_maps_compose_request_location_permission.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.darrylbayliss.net%2Fimages%2Fgoogle_maps_compose_request_location_permission.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Animating the Map&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whilst most map apps require a user to move the map via touch, Google Maps for Compose provides APIs to move the map programmatically. This can be useful if you want to navigate to an specific area in response to an event.&lt;/p&gt;

&lt;p&gt;In this example, we’ll gently navigate the app through our collection of markers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contentAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;GoogleMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cameraPositionState&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Clustering&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parkMarkers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;onClusterClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle when the click is tapped&lt;/span&gt;
                &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;onClusterItemClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle when the marker is tapped&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Animation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parkMarkers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;cameraPositionState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nc"&gt;CameraUpdateFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newLatLngZoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;itemPosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// LatLng&lt;/span&gt;
                        &lt;span class="mf"&gt;16.0f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Zoom level&lt;/span&gt;
                      &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="c1"&gt;// Animation duration in millis&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4000L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Delay in millis&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key part here is the code inside the &lt;code&gt;LaunchedEffect&lt;/code&gt;. For each marker the app sets up a &lt;code&gt;cameraPositionState.animate()&lt;/code&gt; call to navigate to the marker. The camera receives this information via an update to the camera, created using &lt;code&gt;CameraUpdateFactory.newLatLngZoom()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This method takes a &lt;code&gt;LatLng&lt;/code&gt;, a float indicating the zoom level of the map and a long to set the duration of the animation.&lt;/p&gt;

&lt;p&gt;Finally, to space the animations we use &lt;code&gt;delay()&lt;/code&gt; to add a 4 second pause between each animation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.darrylbayliss.net%2Fimages%2Fgoogle_maps_compose_animations.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.darrylbayliss.net%2Fimages%2Fgoogle_maps_compose_animations.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Showing Street View&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s not just an aerial map Google Maps for Compose helps you with. You can also give apps access to &lt;strong&gt;Street View&lt;/strong&gt;, showing a 360 view of a location. You do this by using the &lt;code&gt;StreetView&lt;/code&gt; composable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;selectedMarker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ParkItem&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;mutableStateOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedMarker&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;StreetView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;streetViewPanoramaOptionsFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;StreetViewPanoramaOptions&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedMarker&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contentAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;GoogleMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cameraPositionState&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Clustering&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parkMarkers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;onClusterClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle when the cluster is clicked&lt;/span&gt;
                &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;onClusterItemClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle when a marker in the cluster is clicked&lt;/span&gt;
                &lt;span class="n"&gt;selectedMarker&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;
                &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we’re setting the &lt;code&gt;selectedMarker&lt;/code&gt; variable whenever a marker is tapped. If a marker is selected we show Street View, passing in the position of the marker.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.darrylbayliss.net%2Fimages%2Fgoogle_maps_compose_street_view.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.darrylbayliss.net%2Fimages%2Fgoogle_maps_compose_street_view.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drawing Shapes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You may want to draw your own shapes and annotations onto the map. Google Maps for Compose provides a number of composables to do this, in this post we’re going to use the &lt;code&gt;Circle&lt;/code&gt; composable.&lt;/p&gt;

&lt;p&gt;A circle is a good shape to use if your app uses Geofences to react to changes from a users location. A circle can represent the area a geofence is active within.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt; &lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contentAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;GoogleMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cameraPositionState&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Clustering&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parkMarkers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;onClusterClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle when the cluster is clicked&lt;/span&gt;
                &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;onClusterItemClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle when a marker in the cluster is clicked&lt;/span&gt;
                &lt;span class="n"&gt;selectedMarker&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;
                &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;parkMarkers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;120.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;fillColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;strokeColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Green&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we setup a circle for each of our markers. Creating a circle involves passing it a position and the size of the radius for the circle. We also use two optional parameters to set the color of the border and fill color for the circle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfqbad4z34jhqrym0lrj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfqbad4z34jhqrym0lrj.png" alt="Multiple markers with circles"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Showing a ScaleBar&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A good map often comes with legends and diagrams showing what a measure of space on the map is equivalent to in distance. This gives you an idea of the spaces involved in the map, as not every map may use the same form of measurements.&lt;/p&gt;

&lt;p&gt;For digital maps that can zoom in and out, this adds a particular layer of complexity as the distances represented can change dynamically. Fortunately, Google Maps for Compose has you covered.&lt;/p&gt;

&lt;p&gt;Using the Widgets library, you gain access to the &lt;code&gt;DisappearingScaleBar&lt;/code&gt; and &lt;code&gt;ScaleBar&lt;/code&gt; composables. These are UI components that sit at the top of the map, providing users with a measure of distance that changes depending on the zoom level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt; &lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contentAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;GoogleMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cameraPositionState&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// You can also use ScaleBar&lt;/span&gt;
            &lt;span class="nc"&gt;DisappearingScaleBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;align&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TopStart&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;cameraPositionState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cameraPositionState&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="nc"&gt;Clustering&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parkMarkers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;onClusterClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle when the cluster is clicked&lt;/span&gt;
                &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;onClusterItemClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="c1"&gt;// Handle when a marker in the cluster is clicked&lt;/span&gt;
                &lt;span class="n"&gt;selectedMarker&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;
                &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;parkMarkers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;120.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;fillColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;strokeColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Green&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding the composable, you get a ScaleBar that changes depending on the zoom level at the top of the map.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18vsk9wxs824j5zcafkk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18vsk9wxs824j5zcafkk.gif" alt="Scalebar changing on zoom out / zoom in"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Help and Support&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google Maps for Compose is a great way to work with Google Maps and there’s plenty more to learn. Here are a few places I recommend if you need help:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/googlemaps/android-maps-compose" rel="noopener noreferrer"&gt;Google Maps for Compose Repo&lt;/a&gt;: The repo containing the source code for the library. Contains code samples on how to use the library and also where you can submit your bug reports and contributions&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/maps/documentation/android-sdk/" rel="noopener noreferrer"&gt;Google Maps for Android Website&lt;/a&gt;: The place to go to learn the concepts behind Google Maps for Android. These are high level and don’t use the Compose library, but are nevertheless important to know as these are used in the background.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://discord.com/invite/jRteCzP" rel="noopener noreferrer"&gt;Google Maps Platform Discord&lt;/a&gt; The official Discord server for Google Maps. Here you can find people discussing Google Maps for multiple platforms, asking and offering help, and showcasing their own work.&lt;/p&gt;

</description>
      <category>android</category>
      <category>maps</category>
      <category>tutorial</category>
      <category>kotlin</category>
    </item>
  </channel>
</rss>
