<?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: Sidharth Juyal</title>
    <description>The latest articles on DEV Community by Sidharth Juyal (@chunkyguy).</description>
    <link>https://dev.to/chunkyguy</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%2F1989049%2Ffc00de91-850d-43bf-9ecf-dea5be166be2.jpg</url>
      <title>DEV Community: Sidharth Juyal</title>
      <link>https://dev.to/chunkyguy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chunkyguy"/>
    <language>en</language>
    <item>
      <title>Getting started with LiteRT (Tensorflow Lite)</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Mon, 04 Nov 2024 19:00:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/getting-started-with-litert-tensorflow-lite-1df2</link>
      <guid>https://dev.to/chunkyguy/getting-started-with-litert-tensorflow-lite-1df2</guid>
      <description>&lt;p&gt;So Google recently renamed TensorflowLite to LiteRT. And yes that was a genius move indeed. Because now for the first time in my life I actually want to try TFLite … yea, I mean LiteRT.&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%2Fumxqbjvx08a9hnzw96mz.jpg" 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%2Fumxqbjvx08a9hnzw96mz.jpg" alt="Setup" width="556" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the real world you’d ideally think like a regular ML developer and start with discovering a dataset that then you’d use to train a model. And then as a next step you’d invent a problem that could be solved by your trained model.&lt;/p&gt;

&lt;p&gt;But for this experiment we are going to keep things simple and build the hello world of Machine Learning universe, the Dogs vs Cat exercise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;So coincidentally enough I’ve found a trained model from &lt;a href="https://github.com/offfahad/cat-and-dog-detector-app-flutter" rel="noopener noreferrer"&gt;a flutter project&lt;/a&gt; that can tell if a given photo is of a dog or a cat.&lt;/p&gt;

&lt;p&gt;Nice! So then following the instructions at the &lt;a href="https://ai.google.dev/edge/litert/ios/quickstart" rel="noopener noreferrer"&gt;official getting started docs&lt;/a&gt; we need to add tensorflowlite as a dependency to our project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use_frameworks!
# pod 'TensorFlowLiteSwift'
pod 'TensorFlowLiteSwift', '~&amp;gt; 0.0.1-nightly', :subspecs =&amp;gt; ['CoreML', 'Metal']

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

&lt;/div&gt;



&lt;p&gt;In case you’re wondering why are we using the nightly build and not the stable release. It’s because as of today the latest stable release of &lt;code&gt;TensorFlowLiteC&lt;/code&gt; &lt;a href="https://github.com/tensorflow/tensorflow/issues/47400" rel="noopener noreferrer"&gt;won’t work on iOS simulator&lt;/a&gt; but according to the &lt;a href="https://github.com/tensorflow/tensorflow/issues/47400#issuecomment-1126611267" rel="noopener noreferrer"&gt;last comment&lt;/a&gt; on the issue looks like &lt;code&gt;TensorFlowLiteC&lt;/code&gt; is now also shipped as xcframework but only in the nightly releases.&lt;/p&gt;

&lt;p&gt;And then while we wait for the &lt;code&gt;pod install&lt;/code&gt; to finish we can shamelessly rip sample images of various dogs and cats from the &lt;a href="https://www.udacity.com/enrollment/ud190" rel="noopener noreferrer"&gt;Introduction to TensorFlow Lite&lt;/a&gt; udacity course as our test set.&lt;/p&gt;

&lt;p&gt;And we are all set!&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the app
&lt;/h3&gt;

&lt;p&gt;For the UI we just need a simple image view, a label and a button. The button obviously would reveal the answer and then randomly load the next image.&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%2F3wrcl7cef6s6yib4pora.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%2F3wrcl7cef6s6yib4pora.png" alt="Setup" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now for the interesting bit. First we need a &lt;code&gt;PetClassifier&lt;/code&gt; that takes in an image and returns a text.&lt;br&gt;
&lt;/p&gt;

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

  init?(named: String, labels: [String]) {
    // ...
  }


  func labelForImage(_ image: UIImage) -&amp;gt; String? {
    // ...
  }
}

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

&lt;/div&gt;



&lt;p&gt;And then in the UI layer we can use our &lt;code&gt;PetClassifier&lt;/code&gt; to update the label when tapped on the ‘Evaluate’ button.&lt;br&gt;
&lt;/p&gt;

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

  var classifier: PetClassifier?
  @IBOutlet var answerLabel: UILabel!

  override func viewDidLoad() {
    super.viewDidLoad()
    classifier = PetClassifier(named: "dogvscat", labels: ["Cat", "Dog"])
  }

  @IBAction func handleTap() {
    answerLabel.text = classifier?.labelForImage(selectedImage) ?? "Potato"
  }
}

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

&lt;/div&gt;



&lt;p&gt;Finally, loading the model is pretty easy. We just need to instantiate &lt;code&gt;Interpreter&lt;/code&gt; with the path to our tflite model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PetClassifier {
  let interpreter: Interpreter
  let labels: [String]

  init?(named: String, labels: [String]) {
    guard let modelPath = Bundle.main.path(forResource: named, ofType: "tflite") else {
      return nil
    }

    do {
      var options = Interpreter.Options()
      options.threadCount = Self.threadCount
      interpreter = try Interpreter(modelPath: modelPath, options: options)
      self.labels = labels
    } catch {
      print(error)
      return nil
    }
  }

  // ...
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Invoking the model
&lt;/h3&gt;

&lt;p&gt;To get the answer from the model is a 4 step process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prepare input&lt;/li&gt;
&lt;li&gt;Send input data&lt;/li&gt;
&lt;li&gt;Read output data&lt;/li&gt;
&lt;li&gt;Parse output
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*
 * 1. Prepare input
 */
// user provided image
let image: UIImage 
// image size used for training model
let inputWidth = 224
let inputHeight = 224

// convert image to pixel buffer for further manipulation
let pixelBuffer = ImageUtils.pixelBufferCreate(image: image)
// crop image to size used for training model
let scaledPixelBuffer = ImageUtils.pixelBufferCreateWith(
  pixelBuffer: pixelBuffer,
  resizedTo: CGSize(width: Self.inputWidth, height: Self.inputHeight)
)
// Remove the alpha component from the image buffer to get the RGB data.
let rgbData = ImageUtils.pixelBufferCreateRGBData(
    pixelBuffer: scaledPixelBuffer,
    byteCount: Self.inputWidth * Self.inputHeight * 3
)

/*
 * 2. Send input data 
 */
interpreter.allocateTensors()      
interpreter.copy(rgbData, toInputAt: 0)
interpreter.invoke()

/*
 * 3. Read output data
 */
let outputTensor = try interpreter.output(at: 0)
let results: [Float] = outputTensor.data.withUnsafeBytes {
  Array($0.bindMemory(to: Float.self))
}

/*
 * 4. Parse output
 */      
// Create a zipped array of tuples [(labelIndex: Int, confidence: Float)].
// Sort the zipped results by confidence value
let inferences = zip(labels.indices, results)
  .sorted { $0.1 &amp;gt; $1.1 }
  .map { (label: labels[$0.0], confidence: $0.1) }

let bestInference = inferences.first

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

&lt;/div&gt;



&lt;p&gt;And there you have it. That is how offload your brain to your computer.&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%2Fzy867523pk9yila5tgd5.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%2Fzy867523pk9yila5tgd5.png" alt="Setup" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ImageUtils&lt;/code&gt; from this experiment are available &lt;a href="https://gist.github.com/chunkyguy/1e5244c4dcee8436d700f99380629989" rel="noopener noreferrer"&gt;here&lt;/a&gt;. But there are probably better libraries for these operations. For example, the &lt;a href="https://github.com/hollance/CoreMLHelpers" rel="noopener noreferrer"&gt;CoreMLHelpers&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.googleblog.com/en/tensorflow-lite-is-now-litert/" rel="noopener noreferrer"&gt;TensorFlow Lite is now LiteRT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kaggle.com/models?framework=tfLite" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/models?library=tflite" rel="noopener noreferrer"&gt;HuggingFace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference/ios" rel="noopener noreferrer"&gt;LLM Inference guide for iOS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>How to make an accordion with React Native</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Thu, 17 Oct 2024 19:30:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/how-to-make-an-accordion-with-react-native-1aia</link>
      <guid>https://dev.to/chunkyguy/how-to-make-an-accordion-with-react-native-1aia</guid>
      <description>&lt;p&gt;In the journey of making complex animations with React Native, let’s make an accordion!&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%2F75jii9xudy59oz56ugwk.jpg" 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%2F75jii9xudy59oz56ugwk.jpg" alt="meme" width="500" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don’t know what is the best term for this component. I call it accordion, others call it expandable list. In the first version of material design this component was called as &lt;a href="https://m1.material.io/components/expansion-panels.html" rel="noopener noreferrer"&gt;“Expansion Panel”&lt;/a&gt;. In the latest version they simply don’t talk about it anymore. In Apple’s Human Interface Guidelines they named it as &lt;a href="https://developer.apple.com/design/human-interface-guidelines/disclosure-controls" rel="noopener noreferrer"&gt;“Disclosure controls”&lt;/a&gt; and in SwiftUI for some reasons they called it &lt;code&gt;DisclosureGroup&lt;/code&gt;. But the &lt;a href="https://developer.apple.com/documentation/SwiftUI/DisclosureGroup" rel="noopener noreferrer"&gt;documentation writer&lt;/a&gt; described the component as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A view that shows or hides another content view, based on the state of a disclosure control.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes that is exactly what I mean.&lt;/p&gt;

&lt;p&gt;I’m going to use the assets from the &lt;a href="https://www.frontendmentor.io/challenges/faq-accordion-wyfFdeBwBz" rel="noopener noreferrer"&gt;frontendmentor challenge&lt;/a&gt; because it looks nice.&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%2Fms6fz3f1plpsprqmivtt.jpg" 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%2Fms6fz3f1plpsprqmivtt.jpg" alt="Design" width="375" height="812"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;To begin, inspired by Android I dumped all the data under one true global variable called &lt;code&gt;R&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;const R = {
  colors: {
    white: "hsl(0, 0%, 100%)",
    lightPink: "hsl(275, 100%, 97%)",
    grayishPurple: "hsl(292, 16%, 49%)",
    darkPurple: "hsl(292, 42%, 14%)",
  },
  images: {
    background: require("./assets/images/background-pattern.png"),
    iconStar: require("./assets/images/icon-star.png"),
    iconPlus: require("./assets/images/icon-plus.png"),
    iconMinus: require("./assets/images/icon-minus.png"),
  },
  fonts: {
    regular: {
      name: "WorkSans-Regular",
      weight: 400,
    },
    semibold: {
      name: "WorkSans-SemiBold",
      weight: 600,
    },
    bold: {
      name: "WorkSans-Bold",
      weight: 700,
    },
  },
  strings: {
    header: "FAQs",
    content: [
      {
        title: "What is Frontend Mentor, and how will it help me?",
        body: "Frontend Mentor offers realistic coding challenges to help developers improve their frontend coding skills with projects in HTML, CSS, and JavaScript. It's suitable for all levels and ideal for portfolio building.",
      },
      {
        title: "Is Frontend Mentor free?",
        body: "Yes, Frontend Mentor offers both free and premium coding challenges, with the free option providing access to a range of projects suitable for all skill levels.",
      },
      {
        title: "Can I use Frontend Mentor projects in my portfolio?",
        body: "Yes, you can use projects completed on Frontend Mentor in your portfolio. It's an excellent way to showcase your skills to potential employers!",
      },
      {
        title: "How can I get help if I'm stuck on a challenge?",
        body: "The best place to get help is inside Frontend Mentor's Discord community. There's a help channel where you can ask questions and seek support from other community members.",
      },
    ],
  },
};

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Layout
&lt;/h3&gt;

&lt;p&gt;The first step of making an accordion is to make a simple list with all elements all expanded out. Which in code looks something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function App() {
  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;StatusBar style="light" /&amp;gt;
      &amp;lt;Image style={styles.backgroundImage} source={R.images.background} /&amp;gt;
      &amp;lt;View style={styles.scrollContainer}&amp;gt;
        &amp;lt;ScrollView
          style={styles.scrollView}
          showsVerticalScrollIndicator={false}
        &amp;gt;
          &amp;lt;View style={styles.contentHeader}&amp;gt;
            &amp;lt;Image source={R.images.iconStar} /&amp;gt;
            &amp;lt;Text style={styles.contentHeaderTitle}&amp;gt;{R.strings.header}&amp;lt;/Text&amp;gt;
          &amp;lt;/View&amp;gt;
          {R.strings.content.map(({ title, body }, index) =&amp;gt; {
            return (
              &amp;lt;View
                key={title}
                style={[
                  styles.cell,
                  {
                    borderBottomWidth:
                      index === R.strings.content.length - 1 ? 0 : 1,
                  },
                ]}
              &amp;gt;
                &amp;lt;Pressable style={styles.cellHeader}&amp;gt;
                  &amp;lt;Text style={styles.cellTitle}&amp;gt;{title}&amp;lt;/Text&amp;gt;
                  &amp;lt;Image style={styles.cellIcon} source={R.images.iconMinus} /&amp;gt;
                &amp;lt;/Pressable&amp;gt;
                &amp;lt;Text style={styles.cellBody}&amp;gt;{body}&amp;lt;/Text&amp;gt;
              &amp;lt;/View&amp;gt;
            );
          })}
        &amp;lt;/ScrollView&amp;gt;
      &amp;lt;/View&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: R.colors.lightPink,
  },
  backgroundImage: {
    width: "100%",
    resizeMode: "cover",
  },
  scrollContainer: {
    position: "absolute",
    backgroundColor: R.colors.white,
    borderRadius: 16,
    width: Dimensions.get("window").width - 32,
    height: Dimensions.get("window").height - 150,
    top: 150,
    left: 16,
    right: 16,
    padding: 32,
  },
  scrollView: {
    backgroundColor: "transparent",
  },
  contentHeader: {
    flexDirection: "row",
    gap: 32,
    paddingVertical: 8,
  },
  contentHeaderTitle: {
    fontSize: 36,
    fontFamily: R.fonts.bold.name,
    fontWeight: R.fonts.bold.weight,
  },
  cell: {
    paddingVertical: 16,
    borderBottomColor: R.colors.lightPink,
  },
  cellHeader: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-between",
  },
  cellTitle: {
    fontSize: 20,
    fontFamily: R.fonts.semibold.name,
    fontWeight: R.fonts.semibold.weight,
    width: "90%",
  },
  cellBody: {
    fontSize: 16,
    fontFamily: R.fonts.regular.name,
    lineHeight: 22,
    color: R.colors.grayishPurple,
    paddingTop: 16,
  },
});

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

&lt;/div&gt;



&lt;p&gt;The interesting bits are with creating a z-stack layout, with an image view in the background and a scroll view in front.&lt;/p&gt;

&lt;p&gt;The tricky thing with &lt;code&gt;ScrollView&lt;/code&gt; is that it needs a fixed frame and flexible content size. And what this actually means in ReactNative is that the parent of &lt;code&gt;ScrollView&lt;/code&gt; should have a fixed size and then the children can be whatever.&lt;/p&gt;

&lt;p&gt;But the container view also needs a &lt;code&gt;position: absolute&lt;/code&gt; so we can move it in front of the image. And this bit of css does exactly all of that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  scrollContainer: {
    position: "absolute",
    width: Dimensions.get("window").width - 32,
    height: Dimensions.get("window").height - 150,
    top: 150,
    left: 16,
    right: 16,
    // ...
  }

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

&lt;/div&gt;



&lt;p&gt;If you’re coming from a UIKit background like me and if you squint your eyes a bit this math looks like what you need to do for &lt;code&gt;CGRect&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And with our basic setup is done&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%2Fzl26my2wov1q7njh5zp1.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%2Fzl26my2wov1q7njh5zp1.gif" alt="Basic Setup" width="148" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  State management
&lt;/h3&gt;

&lt;p&gt;Next step in making an accordion is to handle the state. And by state I mean either the selected index or an array of indices.&lt;/p&gt;

&lt;p&gt;In code this means to first introduce &lt;code&gt;useState&lt;/code&gt; and then using the &lt;code&gt;Pressable&lt;/code&gt; to update the state. And finally the UI to consume the state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function App() {
  const [selectedIndex, setSelectedIndex] = useState(0);
  // ...
    &amp;lt;Pressable
      style={styles.cellHeader}
      onPress={() =&amp;gt; setSelectedIndex(index)}
    &amp;gt;
      &amp;lt;Text style={styles.cellTitle}&amp;gt;{title}&amp;lt;/Text&amp;gt;
      &amp;lt;Image
        style={styles.cellIcon}
        source={
          selectedIndex === index
            ? R.images.iconMinus
            : R.images.iconPlus
        }
      /&amp;gt;
    &amp;lt;/Pressable&amp;gt;
    {selectedIndex === index &amp;amp;&amp;amp; (
      &amp;lt;Text style={styles.cellBody}&amp;gt;{body}&amp;lt;/Text&amp;gt;
    )}
  // ...
}

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

&lt;/div&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%2F0e2xndpw9ouur0o72g5e.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%2F0e2xndpw9ouur0o72g5e.gif" alt="Basic Setup" width="148" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Animate changes
&lt;/h3&gt;

&lt;p&gt;And the final step is to animate the changes. In ReactNative that means using Reanimated to animate the height.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function Cell({ text, isOpen }) {
  const cellContainerStyle = useAnimatedStyle(() =&amp;gt; {
    const height = isOpen ? withTiming(100) : withTiming(0);
    return { height };
  });
  return (
    &amp;lt;Animated.View style={cellContainerStyle}&amp;gt;
      &amp;lt;Text style={styles.cellBody}&amp;gt;{text}&amp;lt;/Text&amp;gt;
    &amp;lt;/Animated.View&amp;gt;
  );
}

&amp;lt;Cell 
  text={body} 
  isOpen={index === selectedIndex} /&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Now to calculate the actual expanded height, we need to first calculate the max height of the &lt;code&gt;Cell&lt;/code&gt; by wrapping the text within a &lt;code&gt;View&lt;/code&gt; with &lt;code&gt;position: absolute&lt;/code&gt; and using the &lt;code&gt;onLayout&lt;/code&gt; callback to record the evaluated height.&lt;/p&gt;

&lt;p&gt;Then, we need to make sure the &lt;code&gt;Animated.View&lt;/code&gt; has &lt;code&gt;overflow: hidden&lt;/code&gt; to clip the content from being rendered outside of the box.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function Cell({ text, isOpen }) {
  const [maxHeight, setMaxHeight] = useState(0);
  const cellContainerStyle = useAnimatedStyle(() =&amp;gt; {
    const height = isOpen ? withTiming(maxHeight) : withTiming(0);
    return { height };
  });
  return (
    &amp;lt;Animated.View style={[
      cellContainerStyle, 
      { overflow: "hidden" }
      ]}&amp;gt;
      &amp;lt;View
        style={
          { position: "absolute" }
        }
        onLayout={(e) =&amp;gt; {
          if (maxHeight === 0) {
            setMaxHeight(e.nativeEvent.layout.height);
          }
        }}
      &amp;gt;
        &amp;lt;Text style={styles.cellBody}&amp;gt;{text}&amp;lt;/Text&amp;gt;
      &amp;lt;/View&amp;gt;
    &amp;lt;/Animated.View&amp;gt;
  );
}

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

&lt;/div&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%2F24po8s9y1jkszx0494dr.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%2F24po8s9y1jkszx0494dr.gif" alt="Animation" width="148" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is how you build an accordion with ReactNative.&lt;/p&gt;

&lt;p&gt;The code from this experiment is available on &lt;a href="https://github.com/chunkyguy/frontendmentor/blob/main/faq-accordion/app/App.js" rel="noopener noreferrer"&gt;https://github.com/chunkyguy/frontendmentor&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://reactnative.dev/docs/view#onlayout" rel="noopener noreferrer"&gt;https://reactnative.dev/docs/view#onlayout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/NzrJJLSbWf8?si=W1uXuSHyIWVEeWWE" rel="noopener noreferrer"&gt;Animated Collapsible Cards in React Native - Easier Than You Think&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>MoveMe - SwiftUI Edition</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Thu, 12 Sep 2024 18:38:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/moveme-swiftui-edition-2j3l</link>
      <guid>https://dev.to/chunkyguy/moveme-swiftui-edition-2j3l</guid>
      <description>&lt;p&gt;Taking about gestures and animation with SwiftUI is actually not as intuitive as it sounds. But how hard could it be?&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%2Fja4ih4qzpn1cv7xypbzt.jpg" 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%2Fja4ih4qzpn1cv7xypbzt.jpg" alt="Best way to build an app is with Swift and SwiftUI" width="666" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;So like always we need 3 squares nicely lined up in the center of the screen. I’m going to use &lt;code&gt;ZStack&lt;/code&gt; because I want the squares to layout independently of each other. The only thing the parent container view needs to provide is the initial position.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct SquareView: View {
  var position: CGPoint

  var body: some View {
    RoundedRectangle(cornerRadius: 25.0, style: .continuous)
      .frame(width: 100, height: 100)
      .position(position)
      .foregroundStyle(.blue)
  }
}

struct ContentView: View {
  var body: some View {
    ZStack {
      ForEach(0..&amp;lt;3) { idx in
        GeometryReader { geometry in
          SquareView(position: CGPoint(
            x: geometry.size.width * 0.5,
            y: geometry.size.height * (CGFloat(idx + 1) / 4.0)
          ))
        }
      }
    }
  }
}

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

&lt;/div&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%2F5uwgqqu1frv448xmd1gs.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%2F5uwgqqu1frv448xmd1gs.png" alt="setup" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Gestures
&lt;/h3&gt;

&lt;p&gt;Next we need a tap gesture and when detected the selected square should become red. The obvious solution would be to use a &lt;code&gt;TapGesture&lt;/code&gt; but the &lt;code&gt;TapGesture&lt;/code&gt; only activates at touch end and what we need is a way to detect touch began. The actual solution I found is to use the &lt;code&gt;DragGesture&lt;/code&gt; with &lt;code&gt;minimumDistance&lt;/code&gt; set to &lt;code&gt;0&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;struct SquareView: View {
  var position: CGPoint
  @State var isSelected = false

  var body: some View {
    RoundedRectangle(cornerRadius: 25.0, style: .continuous)
      .frame(width: 100, height: 100)
      .position(position)
      .foregroundStyle(isSelected ? .red : .blue)
      .gesture(
        DragGesture(minimumDistance: 0)
          .onChanged({ _ in isSelected = true })
          .onEnded({ _ in isSelected = false })
      )
  }
}

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

&lt;/div&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%2F2b0o1glp1wqf2swcmfr3.gif" 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%2F2b0o1glp1wqf2swcmfr3.gif" alt="touch" width="148" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next we need to square to scale up when selected. Again the obvious solution is to use the &lt;code&gt;scaleEffect&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;struct SquareView: View {
  var position: CGPoint
  @State var isSelected = false

  var body: some View {
    RoundedRectangle(cornerRadius: 25.0, style: .continuous)
      .frame(width: 100, height: 100)
      .position(position)
      .foregroundStyle(isSelected ? .red : .blue)
      .scaleEffect(isSelected ? 1.2 : 1)
      .gesture(
        DragGesture(minimumDistance: 0)
          .onChanged({ _ in isSelected = true })
          .onEnded({ _ in isSelected = false })
      )
  }
}

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

&lt;/div&gt;



&lt;p&gt;But this brings another problem. Notice how the squares are not centered when scaling up.&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%2Fbukqtt18boflw807iytm.gif" 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%2Fbukqtt18boflw807iytm.gif" alt="scale-bug" width="148" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is because the view tree in SwiftUI is inverted because each modifier creates a new &lt;code&gt;View&lt;/code&gt; and wraps the invoking object as its child. So these two are equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Box()
 .firstModifier()
 .secondModifier()


SecondModifierView(
  FirstModifierView(
    Box()
  )
)

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

&lt;/div&gt;



&lt;p&gt;So back in our solution above the &lt;code&gt;scaleEffect&lt;/code&gt; is applied first and then the &lt;code&gt;position&lt;/code&gt;. This make the center to become off centered. The solution is to apply the &lt;code&gt;position&lt;/code&gt; modifier after the &lt;code&gt;scaleEffect&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;struct SquareView: View {
  var position: CGPoint
  @State var isSelected = false

  var body: some View {
    RoundedRectangle(cornerRadius: 25.0, style: .continuous)
      .frame(width: 100, height: 100)
      .scaleEffect(isSelected ? 1.2 : 1)
      .position(position)
      .foregroundStyle(isSelected ? .red : .blue)
      .gesture(
        DragGesture(minimumDistance: 0)
          .onChanged({ _ in isSelected = true })
          .onEnded({ _ in isSelected = false })
      )
  }
}

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

&lt;/div&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%2F7khnod2g3wnsjkarbkxy.gif" 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%2F7khnod2g3wnsjkarbkxy.gif" alt="scale" width="148" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally we want the square to move around with the finger. This part is simple since we already have drag gesture, we just need to update the position of the square.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct SquareView: View {
  @State var position: CGPoint
  @State var isSelected = false

  var body: some View {
    RoundedRectangle(cornerRadius: 25.0, style: .continuous)
      .frame(width: 100, height: 100)
      .scaleEffect(isSelected ? 1.2 : 1)
      .position(position)
      .foregroundStyle(isSelected ? .red : .blue)
      .gesture(
        DragGesture(minimumDistance: 0)
          .onChanged({ value in
            isSelected = true
            position = value.location
          })
          .onEnded({ _ in isSelected = false })
      )
  }
}

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

&lt;/div&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%2F3zabs462q8ds899g4s11.gif" 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%2F3zabs462q8ds899g4s11.gif" alt="drag" width="148" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Animations
&lt;/h3&gt;

&lt;p&gt;For the next part we would like the transitions to animate. For this we can either use the &lt;code&gt;withAnimation&lt;/code&gt; block&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct SquareView: View {
  @State var position: CGPoint
  @State var isSelected = false

  var body: some View {
    RoundedRectangle(cornerRadius: 25.0, style: .continuous)
      .frame(width: 100, height: 100)
      .scaleEffect(isSelected ? 1.2 : 1)
      .position(position)
      .foregroundStyle(isSelected ? .red : .blue)
      .gesture(
        DragGesture(minimumDistance: 0)
          .onChanged({ value in
            withAnimation {
              isSelected = true
            }
            position = value.location
          })
          .onEnded({ _ in
            withAnimation {
              isSelected = false
            }
          })
      )
  }
}

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

&lt;/div&gt;



&lt;p&gt;But look our favorite bug is back again. Notice how the squares are off centered when selected.&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%2Fwgi4lr2hhl0wpc2rb7bm.gif" 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%2Fwgi4lr2hhl0wpc2rb7bm.gif" alt="animation-bug" width="148" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But now we know why this bug exists. We need to guarantee that the position is applied after the scaling. But since we are using the &lt;code&gt;withAnimation&lt;/code&gt; block it sets some flag for the next draw pass to be done with animation. And that means all the changes are animated. But what we want is to have only have the scaling change as animated and everything else non animated.&lt;/p&gt;

&lt;p&gt;To achieve this we can use the &lt;code&gt;animation&lt;/code&gt; modifier. It does the same thing as &lt;code&gt;withAnimation&lt;/code&gt; block but we can control where in the &lt;code&gt;View&lt;/code&gt; hierarchy we want the animation to happen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct SquareView: View {
  @State var position: CGPoint
  @State var isSelected = false

  var body: some View {
    RoundedRectangle(cornerRadius: 25.0, style: .continuous)
      .scaleEffect(isSelected ? 1.2 : 1, anchor: .center)
      .animation(.easeOut, value: isSelected)
      .frame(width: 100, height: 100)
      .position(position)
      .foregroundStyle(isSelected ? .red : .blue)
      .gesture(
        DragGesture(minimumDistance: 0)
          .onChanged({ value in
            isSelected = true
            position = value.location
          })
          .onEnded({ _ in
            isSelected = false
          })
      )
  }
}

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

&lt;/div&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%2Fmsdbuoiiu5n2qyfwxd4v.gif" 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%2Fmsdbuoiiu5n2qyfwxd4v.gif" alt="animation" width="148" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The solution is available on &lt;a href="https://github.com/chunkyguy/MoveMe/tree/main/swiftui" rel="noopener noreferrer"&gt;https://github.com/chunkyguy/MoveMe&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2023/10156" rel="noopener noreferrer"&gt;Explore SwiftUI animation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2023/10157/" rel="noopener noreferrer"&gt;Wind your way through advanced animations in SwiftUI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swift</category>
      <category>swiftui</category>
      <category>ios</category>
      <category>animation</category>
    </item>
    <item>
      <title>Coordinating multiple gesture recognizers</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Wed, 11 Sep 2024 19:53:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/coordinating-multiple-gesture-recognizers-550j</link>
      <guid>https://dev.to/chunkyguy/coordinating-multiple-gesture-recognizers-550j</guid>
      <description>&lt;p&gt;So how does one actually work with multiple gesture recognizers on same view?&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%2Fcuajcctkagv0dnf2nki5.jpg" 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%2Fcuajcctkagv0dnf2nki5.jpg" alt="UIGestureRecognizer is now my best friend" width="666" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s recreate the &lt;strong&gt;MoveMe&lt;/strong&gt; sample with &lt;code&gt;UIGestureRecognizer&lt;/code&gt;. The idea is to have both &lt;code&gt;UILongPressGestureRecognizer&lt;/code&gt; and &lt;code&gt;UIPanGestureRecognizer&lt;/code&gt; play nicely with each other. With &lt;code&gt;UILongPressGestureRecognizer&lt;/code&gt; responsible for detecting selection and &lt;code&gt;UIPanGestureRecognizer&lt;/code&gt; responsible for dragging the selected squares.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;We have a &lt;code&gt;SquareView&lt;/code&gt; that can react to changes such as selection and change in position. The main updates happen in &lt;code&gt;layoutSubviews&lt;/code&gt;. &lt;code&gt;setNeedsLayout&lt;/code&gt; reschedules a update at next draw cycle and &lt;code&gt;layoutIfNeeded&lt;/code&gt; requests an update immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct SquareViewProps {
  var color = UIColor.blue
  var scale: CGFloat = 1.0
  var position: CGPoint
}

class SquareView: UIView {

  private var props: SquareViewProps {
    didSet { setNeedsLayout() }
  }

  override init(frame: CGRect) {
    props = SquareViewProps(position: CGPoint(x: frame.midX, y: frame.midY))
    super.init(frame: frame)
  }

  override func layoutSubviews() {
    super.layoutSubviews()
    backgroundColor = props.color
    center = props.position
    transform = CGAffineTransform(scaleX: props.scale, y: props.scale)
  }

  private func resetProps(_ props: SquareViewProps) {
    self.props = props
    UIView.animate(withDuration: 0.3, delay: 0, options: [.beginFromCurrentState]) {
      self.layoutIfNeeded()
    }
  }
}

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

&lt;/div&gt;



&lt;p&gt;And then we can host the &lt;code&gt;SquareView&lt;/code&gt; in some parent &lt;code&gt;UIView&lt;/code&gt; or &lt;code&gt;UIViewController&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;class ViewController: UIViewController {

  private var squareVws: [SquareView] = []

  override func viewDidLoad() {
    super.viewDidLoad()

    squareVws = (0..&amp;lt;3).map { idx in
      SquareView(
        frame: CGRect(
          x: view.bounds.midX - 50,
          y: CGFloat.lerp(
            start: (view.bounds.minY + 100),
            end: (view.bounds.maxY - 200),
            factor: CGFloat(idx) / 2
          ),
          width: 100, height: 100
        )
      )
    }

    squareVws.forEach { view.addSubview($0) }
  }  
}

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

&lt;/div&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%2Ffn3yry1eh3csvunv0qfp.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%2Ffn3yry1eh3csvunv0qfp.png" alt="setup" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;Next we can add a &lt;code&gt;UIPanGestureRecognizer&lt;/code&gt; on the &lt;code&gt;ViewController&lt;/code&gt; and forward the gesture events to selected views.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enum GestureEvent {
  case began
  case changed(CGPoint)
  case ended
}

class SquareView: UIView {
    func handleGestureEvent(_ event: GestureEvent) {
    switch event {
    case .began:
      resetProps(SquareViewProps(
        color: .red,
        scale: 1.2,
        position: props.position
      ))

    case .changed(let translation):
      props.position = CGPoint.add(translation, props.position)

    case .ended:
      resetProps(SquareViewProps(position: props.position))
    }
  }

  // ...
}

class ViewController: UIViewController {

  private var squareVws: [SquareView] = []
  private var selectedVws: [SquareView] = []

  override func viewDidLoad() {
    // ...

    let dragGesture = UIPanGestureRecognizer(
      target: self,
      action: #selector(handleDrag)
    )

    view.addGestureRecognizer(dragGesture)
  }

  @objc func handleDrag(_ sender: UIPanGestureRecognizer) {
    switch sender.state {
    case .began:
      let pt = sender.location(in: view)
      selectedVws = squareVws.filter { $0.frame.contains(pt) }
      handleGestureEvent(.began)

    case .changed:
      let translation = sender.translation(in: view)
      handleGestureEvent(.changed(translation))
      sender.setTranslation(.zero, in: view)

    case .ended:
      handleGestureEvent(.ended)
      selectedVws = []

    default:
      break
    }
  }

  func handleGestureEvent(_ event: GestureEvent) {
    selectedVws.forEach { $0.handleGestureEvent(event) }
  }
}

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

&lt;/div&gt;



&lt;p&gt;So far so good but with this implementation we receive gesture events after the drag has started but we want to receive the &lt;code&gt;.began&lt;/code&gt; as soon as the user touches the square. We can use the &lt;code&gt;UITapGestureRecognizer&lt;/code&gt; but it only activates at touch up and not at touch down. So either we need to rollout our own gesture or ‘hack’ &lt;code&gt;UILongPressGestureRecognizer&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;class ViewController: UIViewController {

  private var squareVws: [SquareView] = []
  private var selectedVws: [SquareView] = []

  override func viewDidLoad() {
    super.viewDidLoad()

    // ...

    let tapGesture = UILongPressGestureRecognizer(
      target: self,
      action: #selector(handleTap)
    )
    tapGesture.minimumPressDuration = 0.1

    let dragGesture = UIPanGestureRecognizer(
      target: self,
      action: #selector(handleDrag)
    )

    view.addGestureRecognizer(tapGesture)
    view.addGestureRecognizer(dragGesture)
  }

  @objc func handleTap(_ sender: UILongPressGestureRecognizer) {
      switch sender.state {
      case .began:
        let pt = sender.location(in: view)
        selectedVws = squareVws.filter { $0.frame.contains(pt) }
        handleGestureEvent(.began)

      case .ended:
        handleGestureEvent(.ended)
        selectedVws = []

      default:
        break
      }
  }

  @objc func handleDrag(_ sender: UIPanGestureRecognizer) {
    switch sender.state {
    case .changed:
      let translation = sender.translation(in: view)
      handleGestureEvent(.changed(translation))
      sender.setTranslation(.zero, in: view)

    default:
      break
    }
  }

  func handleGestureEvent(_ event: GestureEvent) {
    selectedVws.forEach { $0.handleGestureEvent(event) }
  }
}

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

&lt;/div&gt;



&lt;p&gt;But this poses another problem. On a &lt;code&gt;UIView&lt;/code&gt; by default only one gesture recognizer is active at a time. So once the &lt;code&gt;UILongPressGestureRecognizer&lt;/code&gt; is activated &lt;code&gt;UIPanGestureRecognizer&lt;/code&gt; is ignored.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;Gesture recognizers have a defined order of precedence. So if multiple gesture recognizers are attached to a view, the winner is decided by the default rules. But we can override the rules by implementing the &lt;code&gt;UIGestureRecognizerDelegate&lt;/code&gt;. For our case since each gesture recognizer is listening to different states we can have both the gestures active at the same time&lt;br&gt;
&lt;/p&gt;

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

  // ...    


  override func viewDidLoad() {
    super.viewDidLoad()

    let tapGesture = UILongPressGestureRecognizer()
    let dragGesture = UIPanGestureRecognizer()

    // ...    

    tapGesture.delegate = self
    dragGesture.delegate = self
  }
}

extension ViewController: UIGestureRecognizerDelegate {
  public func gestureRecognizer(
    _ gestureRecognizer: UIGestureRecognizer,
    shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
  ) -&amp;gt; Bool {
    return gestureRecognizer is UILongPressGestureRecognizer
    &amp;amp;&amp;amp; otherGestureRecognizer is UIPanGestureRecognizer
  }
}

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

&lt;/div&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%2Fogdp6arxeq83sg4xem5d.gif" 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%2Fogdp6arxeq83sg4xem5d.gif" alt="setup" width="148" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The solution is available on &lt;a href="https://github.com/chunkyguy/MoveMe/tree/main/uikit" rel="noopener noreferrer"&gt;https://github.com/chunkyguy/MoveMe&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers" rel="noopener noreferrer"&gt;Coordinating multiple gesture recognizers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers/allowing_the_simultaneous_recognition_of_multiple_gestures" rel="noopener noreferrer"&gt;Allowing the simultaneous recognition of multiple gestures&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swift</category>
      <category>uikit</category>
      <category>animation</category>
    </item>
    <item>
      <title>Hello ionic</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Fri, 06 Sep 2024 16:13:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/hello-ionic-g6</link>
      <guid>https://dev.to/chunkyguy/hello-ionic-g6</guid>
      <description>&lt;p&gt;In the next series of how javascript is slowly taking over the tech let’s try Ionic.&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%2Fa6p04ahi799ujhn5fg46.jpg" 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%2Fa6p04ahi799ujhn5fg46.jpg" alt="Javascript is taking over" width="500" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On their home page Ionic Framework calls themselves as &lt;strong&gt;The Cross-Platform App Development Leader&lt;/strong&gt;. Lets find out the truth in that.&lt;/p&gt;

&lt;p&gt;I remember using Cordova back in 2010 and honestly the mobile landscape was very different back then. From what I’ve read so far, ionic was built on top of Cordova and angular.js but there is also an out-of-box template for react, which is what I’m probably going to use today. So let’s go!&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;First start the ionic cli.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install -g @ionic/cli native-run cordova-res&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;ionic start --list&lt;/code&gt; reveals the following list of templates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starters for @ionic/react (--type=react)

name | description
--------------------------------------------------------------------------------------
blank | A blank starter project
list | A starting project with a list
my-first-app | A template for the "Build Your First App" tutorial
sidemenu | A starting project with a side menu with navigation in the content area
tabs | A starting project with a simple tabbed interface

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

&lt;/div&gt;



&lt;p&gt;So I’m obviously going to use the &lt;code&gt;blank&lt;/code&gt; template:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ionic start photoapp blank --type=react --capacitor&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And then few more installs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install @capacitor/camera @capacitor/preferences @capacitor/filesystem&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And then a few more:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install @ionic/pwa-elements&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And then finally &lt;em&gt;a few minutes later&lt;/em&gt; running &lt;code&gt;ionic serve&lt;/code&gt; starts the good old familiar vite react app on the browser. Good, but I thought we are building a mobile app. So let’s fix that.&lt;/p&gt;

&lt;p&gt;First we need to build the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ionic build
ionic cap add ios
ionic cap add android

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

&lt;/div&gt;



&lt;p&gt;Later in development if we wish to copy the web project into iOS, we need to run the &lt;code&gt;ionic cap copy&lt;/code&gt; command. And need to sync after adding plugins we need to run the &lt;code&gt;ionic cap sync&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;And then finally to run the project for iOS we need to run the &lt;code&gt;ionic cap open ios&lt;/code&gt; command. This would open the Xcode project from where we can actually run the app in our favorite simulator.&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%2F92yhp1jz6e2c7ls19ktd.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%2F92yhp1jz6e2c7ls19ktd.png" alt="setup" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawing UI
&lt;/h3&gt;

&lt;p&gt;Very nice. Now let’s switch gears to rendering. Ionic provides a nice list of UI components to avoid lazy devs like me from writing the css. To make a 2 column grid of photos we can make use of &lt;code&gt;IonGrid&lt;/code&gt;. &lt;code&gt;IonGrid&lt;/code&gt; is made up of many &lt;code&gt;IonRow&lt;/code&gt;, and &lt;code&gt;IonRow&lt;/code&gt; is made up of many &lt;code&gt;IonCol&lt;/code&gt; items.&lt;/p&gt;

&lt;p&gt;So make a 2 column grid we can design our grid as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type PhotoTileProps = {
  photo: Photo | null;
};

function PhotoTile(props: PhotoTileProps | null) {
  return (
    props?.photo &amp;amp;&amp;amp; (
      &amp;lt;IonCol&amp;gt;
        &amp;lt;IonImg src={props.photo.thumbnailUrl} /&amp;gt;
      &amp;lt;/IonCol&amp;gt;
    )
  );
}

type PhotoRowProps = {
  left: Photo | null;
  right: Photo | null;
};

function PhotoRow(props: PhotoRowProps) {
  return (
    &amp;lt;IonRow&amp;gt;
      &amp;lt;PhotoTile photo={props.left} /&amp;gt;
      &amp;lt;PhotoTile photo={props.right} /&amp;gt;
    &amp;lt;/IonRow&amp;gt;
  );
}

export default function PhotoList() {
  const [photoList, setPhotoList] = useState&amp;lt;Array&amp;lt;PhotoRowProps&amp;gt;&amp;gt;([]);

  async function fetchData() {
    const response = await fetch("https://jsonplaceholder.typicode.com/photos");
    const content = await response.json();
    const list = content as Photo[];
    let props: Array&amp;lt;PhotoRowProps&amp;gt; = [];
    for (let index = 0; index &amp;lt; list.length; index += 2) {
      const element = list[index];
      let row: PhotoRowProps = {
        left: list[index],
        right: list[index + 1],
      };
      props.push(row);
    }
    setPhotoList(props);
  }

  return (
    &amp;lt;IonGrid&amp;gt;
      {photoList.map((item, index) =&amp;gt; (
        &amp;lt;PhotoRow key={index} left={item.left} right={item.right} /&amp;gt;
      ))}
    &amp;lt;/IonGrid&amp;gt;
  );
}

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

&lt;/div&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%2F9hudyxhi4c759bo2smym.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%2F9hudyxhi4c759bo2smym.png" alt="grid" width="384" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This should work for even numbered elements, but for odd numbered elements our grid would look weird, since the last element would occupy the entire width. I can reproduce this bug by only rendering 3 elements&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%2F98kzlkpf67vj9yhbwv7f.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%2F98kzlkpf67vj9yhbwv7f.png" alt="grid broken" width="384" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An easy fix is to always draw the &lt;code&gt;IonCol&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;function PhotoTile(props: PhotoTileProps) {
  return (
    &amp;lt;IonCol&amp;gt;
      &amp;lt;IonImg src={props.photo?.thumbnailUrl} /&amp;gt;
    &amp;lt;/IonCol&amp;gt;
  );
}

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

&lt;/div&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%2F0ca37wfou3fnba3zf7kl.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%2F0ca37wfou3fnba3zf7kl.png" alt="grid fix" width="384" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For details page we can build a UI similarly.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type PhotoDetailProps = {
  id: string;
};

export default function PhotoDetail(props: PhotoDetailProps) {
  const [photo, setPhoto] = useState&amp;lt;Photo | undefined&amp;gt;(undefined);

  async function fetchData() {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/photos/${props.id}`
    );
    const content = await response.json();
    const photo = content as Photo;
    setPhoto(photo);
  }

  useEffect(() =&amp;gt; {
    fetchData();
  }, []);

  return !photo ? (
    &amp;lt;IonSpinner /&amp;gt;
  ) : (
    &amp;lt;PhotoTile
      photo={photo}
    /&amp;gt;
  );
}

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

&lt;/div&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%2Fxe6e17it0xvvuocs47nu.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%2Fxe6e17it0xvvuocs47nu.png" alt="details" width="384" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation
&lt;/h3&gt;

&lt;p&gt;The boilerplate app already provides a router which looks very familiar because, no surprise, it’s a wrapper around &lt;code&gt;react-router&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;const App: React.FC = () =&amp;gt; (
  &amp;lt;IonApp&amp;gt;
    &amp;lt;IonReactRouter&amp;gt;
      &amp;lt;IonRouterOutlet&amp;gt;
        &amp;lt;Route exact path="/home"&amp;gt;
          &amp;lt;Home /&amp;gt;
        &amp;lt;/Route&amp;gt;
        &amp;lt;Route exact path="/"&amp;gt;
          &amp;lt;Redirect to="/home" /&amp;gt;
        &amp;lt;/Route&amp;gt;
      &amp;lt;/IonRouterOutlet&amp;gt;
    &amp;lt;/IonReactRouter&amp;gt;
  &amp;lt;/IonApp&amp;gt;
);

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

&lt;/div&gt;



&lt;p&gt;And to extend the router to show a details page with param we can add the usual react-router styled &lt;code&gt;Route&lt;/code&gt;. So for our photo app our routes would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const App: React.FC = () =&amp;gt; (
  &amp;lt;IonApp&amp;gt;
    &amp;lt;IonReactRouter&amp;gt;
      &amp;lt;IonRouterOutlet&amp;gt;
        &amp;lt;Route path="/home" component={Home} /&amp;gt;
        &amp;lt;Route path="/details/:id" component={Details} /&amp;gt;
        &amp;lt;Redirect exact from="/" to="/home" /&amp;gt;
      &amp;lt;/IonRouterOutlet&amp;gt;
    &amp;lt;/IonReactRouter&amp;gt;
  &amp;lt;/IonApp&amp;gt;
);

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

&lt;/div&gt;



&lt;p&gt;To parse the path argument we can use the &lt;code&gt;RouteComponentProps&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;interface UserDetailPageProps
  extends RouteComponentProps&amp;lt;{
    id: string;
  }&amp;gt; {}

export default function Details(props: UserDetailPageProps) {
  return (
    &amp;lt;IonPage&amp;gt;
      &amp;lt;IonHeader&amp;gt;
        &amp;lt;IonToolbar&amp;gt;
          &amp;lt;IonTitle&amp;gt;Photo {props.match.params.id}&amp;lt;/IonTitle&amp;gt;
        &amp;lt;/IonToolbar&amp;gt;
      &amp;lt;/IonHeader&amp;gt;
      &amp;lt;IonContent fullscreen&amp;gt;
        &amp;lt;PhotoDetail id={props.match.params.id} /&amp;gt;
      &amp;lt;/IonContent&amp;gt;
    &amp;lt;/IonPage&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;Now to navigate between &lt;code&gt;Home&lt;/code&gt; and &lt;code&gt;Details&lt;/code&gt; we can use the &lt;code&gt;routerLink&lt;/code&gt; that is provided with a lot of Ionic components, like the &lt;code&gt;IonCard&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;type PhotoTileProps = {
  routerLink: string;
  photoUrl: string | undefined;
  photoTitle: string | undefined;
};

function createPhotoTileProps(photo: Photo): PhotoTileProps {
   return {
     routerLink: `/details/${photo.id}`,
     photoUrl: photo.thumbnailUrl,
     photoTitle: undefined,
   };
}

export default function PhotoTile(props: PhotoTileProps) {
  return (
    &amp;lt;IonCard routerLink={props.routerLink}&amp;gt;
      &amp;lt;IonCol&amp;gt;
        &amp;lt;IonImg src={props.photoUrl} /&amp;gt;
        {props.photoTitle &amp;amp;&amp;amp; &amp;lt;IonLabel&amp;gt;{props.photoTitle}&amp;lt;/IonLabel&amp;gt;}
      &amp;lt;/IonCol&amp;gt;
    &amp;lt;/IonCard&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;And there you have it. The photo app with Ionic framework!&lt;/p&gt;

&lt;p&gt;As a final step, let’s build and run the app using Xcode one more time. So running &lt;code&gt;ionic build &amp;amp;&amp;amp; ionic cap copy&lt;/code&gt; one more time. And then running the app from Xcode. Viola!&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%2Fu436t7nd0fpjslr3y1u1.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%2Fu436t7nd0fpjslr3y1u1.png" alt="iOS-home" width="295" height="640"&gt;&lt;/a&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%2F5fithv80cmoks491qr9r.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%2F5fithv80cmoks491qr9r.png" alt="iOS-details" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The details screen seems to be missing the back button. The fix is actually very simple, just add the &lt;code&gt;IonBackButton&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;&amp;lt;IonToolbar&amp;gt;
  &amp;lt;IonButtons slot="start"&amp;gt;
    &amp;lt;IonBackButton defaultHref="#"&amp;gt;&amp;lt;/IonBackButton&amp;gt;
  &amp;lt;/IonButtons&amp;gt;
  &amp;lt;IonTitle&amp;gt;Photo {props.match.params.id}&amp;lt;/IonTitle&amp;gt;
&amp;lt;/IonToolbar&amp;gt;

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

&lt;/div&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%2Flmqr91krc892034keuah.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%2Flmqr91krc892034keuah.png" alt="iOS-details-back" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;I think Ionic is pretty solid framework for web developers. You can almost always tell that this is web UI, and I don’t think ionic is trying to hide that fact. But like with every thing software there are always trade-offs and the fact that Ionic is not limiting the web devs from using 100% of their expertise can be a huge win.&lt;/p&gt;

&lt;p&gt;Like always the code from this experiment is available at &lt;a href="https://github.com/chunkyguy/PhotoApp/tree/master/ionic" rel="noopener noreferrer"&gt;https://github.com/chunkyguy/PhotoApp&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ionicframework.com/" rel="noopener noreferrer"&gt;Ionic Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ionicframework.com/docs/react/your-first-app" rel="noopener noreferrer"&gt;Your First Ionic App: React&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ionicframework.com/docs/components" rel="noopener noreferrer"&gt;UI Components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>ionic</category>
    </item>
    <item>
      <title>React navigation basics</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Sun, 01 Sep 2024 13:37:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/react-navigation-basics-3677</link>
      <guid>https://dev.to/chunkyguy/react-navigation-basics-3677</guid>
      <description>&lt;p&gt;I just realized that I’ve never made the photos app with react.js. So let’s do that today.&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%2Fmq80klvvneh59k9fc2o0.jpg" 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%2Fmq80klvvneh59k9fc2o0.jpg" alt="React vs React Native" width="748" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;I’m going to use vite to build the app with javascript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create vite@latest .
npm install
npm run dev

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

&lt;/div&gt;



&lt;p&gt;And we have our app up and running.&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%2Fvjymc6z0zkhzrcolq14i.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%2Fvjymc6z0zkhzrcolq14i.png" alt="setup" width="800" height="1069"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetch data
&lt;/h3&gt;

&lt;p&gt;To fetch and display data in react is a three step process. First we need &lt;code&gt;useState&lt;/code&gt; to hold the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [photoList, setPhotoList] = useState([]);

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

&lt;/div&gt;



&lt;p&gt;Then we need a &lt;code&gt;useEffect&lt;/code&gt; to fetch the data. One way is to use the promises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  useEffect(() =&amp;gt; {
    fetch("https://jsonplaceholder.typicode.com/photos")
      .then((response) =&amp;gt; response.json())
      .then((content) =&amp;gt; setPhotoList(content));
  }, []);

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

&lt;/div&gt;



&lt;p&gt;Another way is to create an &lt;code&gt;async function&lt;/code&gt; and call it from &lt;code&gt;useEffect&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;  async function fetchData() {
    const response = await fetch("https://jsonplaceholder.typicode.com/photos");
    const content = await response.json();
    setPhotoList(content);
  }

  useEffect(() =&amp;gt; {
    fetchData();
  }, []);

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

&lt;/div&gt;



&lt;p&gt;And then draw the UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawing content
&lt;/h3&gt;

&lt;p&gt;To render the data we can simply use the html unordered list&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (
  &amp;lt;&amp;gt;
    {photoList.length === 0 ? (
      &amp;lt;p&amp;gt;Loading ...&amp;lt;/p&amp;gt;
    ) : (
      &amp;lt;ul&amp;gt;
        {photoList.map(({ id, title, thumbnailUrl }) =&amp;gt; {
          return (
            &amp;lt;li key={id}&amp;gt;
                &amp;lt;img src={thumbnailUrl} alt={title} /&amp;gt;
            &amp;lt;/li&amp;gt;
          );
        })}
      &amp;lt;/ul&amp;gt;
    )}
  &amp;lt;/&amp;gt;
);

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

&lt;/div&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%2F9iabzo0s4v0tlgt2d3mw.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%2F9iabzo0s4v0tlgt2d3mw.png" alt="photo-list" width="382" height="952"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the true spirit of react we can probably move the &lt;code&gt;li&lt;/code&gt; out as a reusable component and call it &lt;code&gt;PhotoTile&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;export function PhotoTile({ title, thumbnailUrl }) {
  return (
    &amp;lt;li className="photoTile"&amp;gt;
      &amp;lt;img src={thumbnailUrl} alt={title} /&amp;gt;
    &amp;lt;/li&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;and reduce our &lt;code&gt;App.jsx&lt;/code&gt; to&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;ul className="photoList"&amp;gt;
  {photoList.map(({ id, title, thumbnailUrl }) =&amp;gt; (
    &amp;lt;PhotoTile key={id} title={title} thumbnailUrl={thumbnailUrl} /&amp;gt;
  ))}
&amp;lt;/ul&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Next, to make the list 2 columns we can use grid, flexbox or one of the infinite other methods out there, but my favorite is to simply use the &lt;code&gt;column-count&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;&amp;lt;ul className="photoList"&amp;gt; 
  ...
&amp;lt;/ul&amp;gt;

.photoList {
  column-count: 2;
}

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

&lt;/div&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%2F09klujj6jbdu4x9e4qbs.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%2F09klujj6jbdu4x9e4qbs.png" alt="two-columns" width="800" height="923"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation
&lt;/h3&gt;

&lt;p&gt;And now the real deal. The &lt;code&gt;react-router-dom&lt;/code&gt;. This changes everything. First we need to install the dependency obviously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install react-router-dom

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

&lt;/div&gt;



&lt;p&gt;Then we need to update the &lt;code&gt;main.jsx&lt;/code&gt; to use the &lt;code&gt;router&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;const router = createBrowserRouter([
  {
    path: "/",
    element: &amp;lt;Home /&amp;gt;,
  },
]);

createRoot(document.getElementById("root")).render(
  &amp;lt;StrictMode&amp;gt;
    &amp;lt;RouterProvider router={router} /&amp;gt;
  &amp;lt;/StrictMode&amp;gt;
);

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

&lt;/div&gt;



&lt;p&gt;To navigate to the details, we need to register the route with params:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const router = createBrowserRouter([
  {
    path: "/",
    element: &amp;lt;Home /&amp;gt;,
  },
  {
    path: "/details/:id",
    element: &amp;lt;Details /&amp;gt;,
  },
]);

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

&lt;/div&gt;



&lt;p&gt;Then in the &lt;code&gt;Details&lt;/code&gt; we can get the param value with &lt;code&gt;useParams()&lt;/code&gt; hook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function Details() {
  const { id } = useParams();
  const [photo, setPhoto] = useState(null);

  async function fetchData(id) {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/photos/${id}`
    );
    const content = await response.json();
    setPhoto(content);
  }

  useEffect(() =&amp;gt; {
    fetchData(id);
  }, [id]);

  return (
    &amp;lt;&amp;gt;
      {!photo ? (
        &amp;lt;Loading /&amp;gt;
      ) : (
        &amp;lt;ul&amp;gt;
          &amp;lt;div className="photoDetails"&amp;gt;
            &amp;lt;PhotoTile title={photo.title} url={photo.url} /&amp;gt;
            &amp;lt;p&amp;gt;{photo.title}&amp;lt;/p&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/ul&amp;gt;
      )}
    &amp;lt;/&amp;gt;
  );
}

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

&lt;/div&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%2Fk7rl3sysvyxpnuc0jp5x.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%2Fk7rl3sysvyxpnuc0jp5x.png" alt="two-columns" width="800" height="830"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then to navigate between screens we need to make use of the &lt;code&gt;Link&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function PhotoTile({ title, url, path }) {
  return (
    &amp;lt;li className="photoTile"&amp;gt;
      &amp;lt;Link to={path}&amp;gt;
        &amp;lt;img src={url} alt={title} /&amp;gt;
      &amp;lt;/Link&amp;gt;
    &amp;lt;/li&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;And then construct the details path from &lt;code&gt;Home&lt;/code&gt; screen like:&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;PhotoTile
  key={id}
  title={title}
  url={thumbnailUrl}
  path={`/details/${id}`}
/&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;And back to home from the &lt;code&gt;Details&lt;/code&gt; screen:&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;PhotoTile title={photo.title} url={photo.url} path={"/"} /&amp;gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;And there we have the basics of a React app with navigation. The &lt;code&gt;react-router&lt;/code&gt; is always evolving, there were some new changes in the &lt;code&gt;v6&lt;/code&gt; which I tried to use. But looks good and works like charm.&lt;/p&gt;

&lt;p&gt;The link from this experiment is available at &lt;a href="https://github.com/chunkyguy/PhotoApp/tree/master/react" rel="noopener noreferrer"&gt;https://github.com/chunkyguy/PhotoApp/tree/master/react&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;react.dev/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vitejs.dev/guide" rel="noopener noreferrer"&gt;vitejs.dev/guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Ul3y1LXxzdU" rel="noopener noreferrer"&gt;Learn React Router v6 In 45 Minutes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactrouter.com/" rel="noopener noreferrer"&gt;reactrouter.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>navigation</category>
    </item>
    <item>
      <title>Let's reanimate</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Tue, 27 Aug 2024 21:43:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/lets-reanimate-4a22</link>
      <guid>https://dev.to/chunkyguy/lets-reanimate-4a22</guid>
      <description>&lt;p&gt;Drawing text and images is one thing but the real test of a UI framework is in how good is it with animating content.&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%2Frdyihbtj339gll0h4p15.jpg" 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%2Frdyihbtj339gll0h4p15.jpg" alt=" " width="582" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And my test for animation is the classic &lt;a href="https://developer.apple.com/library/archive/samplecode/MoveMe/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007315" rel="noopener noreferrer"&gt;MoveMe&lt;/a&gt; based on the Apple’s sample code.&lt;/p&gt;

&lt;p&gt;The idea is to draw three boxes on the screen. When selected the box changes color and scales up and then can be moved around with the drag gesture and eventually restores back to original color and size when released.&lt;/p&gt;

&lt;p&gt;Let’s build that sample using React Native’s Reanimated library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;I’m following the official docs but not using their template. So I’ve a basic project created with the blank template and installed the dependencies&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-expo-app playground --template blank
npx expo install react-native-reanimated
npx expo install react-native-gesture-handler

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

&lt;/div&gt;



&lt;p&gt;Next, I added the plugin&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: ["react-native-reanimated/plugin"],
  };
};

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

&lt;/div&gt;



&lt;p&gt;And then draw 3 squares on screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { StatusBar } from "expo-status-bar";
import { StyleSheet, View } from "react-native";
import Animated from "react-native-reanimated";

function Square() {
  return &amp;lt;Animated.View style={styles.square}&amp;gt;&amp;lt;/Animated.View&amp;gt;;
}

export default function App() {
  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;StatusBar style="auto" /&amp;gt;
      &amp;lt;Square /&amp;gt;
      &amp;lt;Square /&amp;gt;
      &amp;lt;Square /&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "space-evenly",
  },
  square: {
    width: 100,
    height: 100,
    backgroundColor: "blue",
  },
});

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

&lt;/div&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%2Fqnour4as4i0lljz73cf4.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%2Fqnour4as4i0lljz73cf4.png" alt="set up" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add gesture handler
&lt;/h3&gt;

&lt;p&gt;To add support for gesture handlers we first need to wrap the content within the &lt;code&gt;GestureHandlerRootView&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;&amp;lt;GestureHandlerRootView style={styles.container}&amp;gt;
  &amp;lt;Square /&amp;gt;
  &amp;lt;Square /&amp;gt;
  &amp;lt;Square /&amp;gt;
&amp;lt;/GestureHandlerRootView&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;And then wrap each &lt;code&gt;Square&lt;/code&gt; within &lt;code&gt;GestureDetector&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;function Square() {
  const gesture = Gesture.Pan();

  return (
    &amp;lt;GestureDetector gesture={gesture}&amp;gt;
      &amp;lt;Animated.View style={styles.square} /&amp;gt;
    &amp;lt;/GestureDetector&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handle gesture events
&lt;/h3&gt;

&lt;p&gt;To handle gesture we first need to create a &lt;code&gt;SharedValue&lt;/code&gt; which is like &lt;code&gt;State&lt;/code&gt; but for animation states. For example, to change the background color when selected we need to listen to &lt;code&gt;onBegin&lt;/code&gt; and &lt;code&gt;onFinalize&lt;/code&gt; events and update the style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function Square() {
  const isPressed = useSharedValue(false);
  const animStyle = useAnimatedStyle(() =&amp;gt; {
    return {
      backgroundColor: isPressed.value ? "red" : "blue",
    };
  });

  const gesture = Gesture.Pan()
    .onBegin(() =&amp;gt; {
      isPressed.value = true;
    })
    .onFinalize(() =&amp;gt; {
      isPressed.value = false;
    });

  return (
    &amp;lt;GestureDetector gesture={gesture}&amp;gt;
      &amp;lt;Animated.View style={[styles.square, animStyle]} /&amp;gt;
    &amp;lt;/GestureDetector&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;Supporting drag is similar. We need to store start and current positions and then update the current position on &lt;code&gt;onChange&lt;/code&gt; event. The &lt;code&gt;onChange&lt;/code&gt; provides the delta change that we then need to add to the start position to calculate the final current position. And then, finally at the &lt;code&gt;onFinalize&lt;/code&gt; event we can sync the start and current positions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function Square() {
  const isPressed = useSharedValue(false);
  const startPos = useSharedValue({ x: 0, y: 0 });
  const pos = useSharedValue({ x: 0, y: 0 });
  const animStyle = useAnimatedStyle(() =&amp;gt; {
    return {
      backgroundColor: isPressed.value ? "red" : "blue",
      transform: [
        { translateX: pos.value.x },
        { translateY: pos.value.y },
        { scale: withSpring(isPressed.value ? 1.2 : 1) },
      ],
    };
  });

  const gesture = Gesture.Pan()
    .onBegin(() =&amp;gt; {
      isPressed.value = true;
    })
    .onChange((e) =&amp;gt; {
      pos.value = {
        x: startPos.value.x + e.translationX,
        y: startPos.value.y + e.translationY,
      };
    })
    .onFinalize(() =&amp;gt; {
      isPressed.value = false;
      startPos.value = {
        x: pos.value.x,
        y: pos.value.y,
      };
    });

  return (
    &amp;lt;GestureDetector gesture={gesture}&amp;gt;
      &amp;lt;Animated.View style={[styles.square, animStyle]} /&amp;gt;
    &amp;lt;/GestureDetector&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;And there you have 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%2Fmm95zcxte45vodqj999x.gif" 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%2Fmm95zcxte45vodqj999x.gif" alt="final" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.swmansion.com/react-native-reanimated" rel="noopener noreferrer"&gt;react-native-reanimated&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.swmansion.com/react-native-gesture-handler" rel="noopener noreferrer"&gt;react-native-gesture-handler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/4HUreYYoE6U?si=rZ53Yvft9nbdlGAC" rel="noopener noreferrer"&gt;The basics of PanGestureHandler with React Native Reanimated 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/chunkyguy/data-driven-ui-with-uikit-o7a-temp-slug-8308582"&gt;Data-Driven UI with UIKit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>reactnative</category>
      <category>animation</category>
    </item>
    <item>
      <title>Jetpack compose navigation</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Fri, 16 Aug 2024 23:20:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/jetpack-compose-navigation-c9n</link>
      <guid>https://dev.to/chunkyguy/jetpack-compose-navigation-c9n</guid>
      <description>&lt;p&gt;It’s Friday night! And you know what that means right? It’s time to dig deeper into Jetpack Compose. And today I would like to try out the navigation between screens with Jetpack Compose.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgflip.com/i/90jgo1" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9a6RZOvE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgflip.com/90jgo1.jpg" title="made at imgflip.com" width="666" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How hard can it?&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;Installing dependencies is never fun in an Android project. First you need to find the &lt;code&gt;libs.version.toml&lt;/code&gt; file and add these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[version]
androidx-navigation = "2.7.7"
kotlinxSerializationJson = "1.6.3"

[libraries]
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }

[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

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

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;gradle sync&lt;/code&gt;. Then open the &lt;code&gt;build.gradle.kts&lt;/code&gt; within the &lt;strong&gt;app/&lt;/strong&gt; and apply the plugin and add the dependency&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins {
  // ...
  alias(libs.plugins.kotlin.serialization)
}

dependencies {
  // ...
  implementation(libs.androidx.navigation.compose)
  implementation(libs.kotlinx.serialization.json)
}

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

&lt;/div&gt;



&lt;p&gt;And then do another round of &lt;code&gt;gradle sync&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If all went fine so far, then you’re all set for writing the code. If not, delete your project and start from scratch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Navigation in Android works with the help of these 2 components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NavController&lt;/code&gt;: Top level object to navigate between screens&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NavHost&lt;/code&gt;: Container within which all routing happens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So to make this work, we first need to inject the &lt;code&gt;NavController&lt;/code&gt; at the root level of the app. Like say the &lt;code&gt;MainActivity&lt;/code&gt; or the root composable like &lt;code&gt;PhotoApp&lt;/code&gt; in our case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun PhotoApp(
  navController: NavHostController = rememberNavController()
) { .. }

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

&lt;/div&gt;



&lt;p&gt;And then simply setup all the available routes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NavHost(
  navController = navController,
  startDestination = "home"
) {
  composable("home") {
    HomeScreen(
      onSelectPhoto = { navController.navigate("photos/${it.id}") },
      modifier = Modifier.fillMaxSize()
    )
  }

  composable("photos/{id}") {
    DetailScreen(
      id = it.arguments?.getString("id") ?: "0",
      modifier = Modifier.fillMaxSize()
    )
  }
}

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

&lt;/div&gt;



&lt;p&gt;And that is all there to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thoughts
&lt;/h3&gt;

&lt;p&gt;Now of course, one can go crazy with typed routing with sealed classes and what not. But this is the gist of it.&lt;/p&gt;

&lt;p&gt;Just like React Native, the path arguments are by default string types and if there’s more data to be passed between the screens we need to come up with our strategy.&lt;/p&gt;

&lt;p&gt;The good thing is the the back button comes for free with Android OS. But for custom back button we again need to devise our own tooling.&lt;/p&gt;

&lt;p&gt;So there is how you navigate between screens with Android. The code for this experiment is up there sitting in the cloud nicely with the rest of the PhotoApp.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/chunkyguy/PhotoApp/tree/master/kotlin" rel="noopener noreferrer"&gt;https://github.com/chunkyguy/PhotoApp/tree/master/kotlin&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/develop/ui/compose/navigation" rel="noopener noreferrer"&gt;Navigation with Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/codelabs/basic-android-kotlin-compose-navigation" rel="noopener noreferrer"&gt;Navigate between screens with Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=AIC_OFQ1r3k&amp;amp;t=141s" rel="noopener noreferrer"&gt;Type-Safe Navigation with the OFFICIAL Compose Navigation Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/guide/navigation" rel="noopener noreferrer"&gt;Navigation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=glyqjzkc4fk" rel="noopener noreferrer"&gt;Navigation Basics in Jetpack Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4gUeyNkGE3g" rel="noopener noreferrer"&gt;Jetpack Compose Navigation for Beginners - Android Studio Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kotlin</category>
      <category>navigation</category>
      <category>jetpack</category>
    </item>
    <item>
      <title>Dear expo, who are you?</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Tue, 23 Jul 2024 20:58:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/dear-expo-who-are-you-35fb</link>
      <guid>https://dev.to/chunkyguy/dear-expo-who-are-you-35fb</guid>
      <description>&lt;p&gt;Let’s try expo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgflip.com/i/8ymeiz" rel="noopener noreferrer"&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%2Fl9tmiidgpbca47dz44dx.jpg" title="made at imgflip.com" width="600" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The motivation behind this experiment is neither my eternal curiosity for exploring new technologies nor is this post sponsored by expo in any way. Rather it is the fact that the nice folks at React Native have killed our beloved React Native CLI. You don’t even see the instructions to create React Native apps using the CLI anymore, as if it never existed! So, without losing another tear on the past let’s take a sneak peek into the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up
&lt;/h3&gt;

&lt;p&gt;The official command on &lt;a href="https://reactnative.dev/docs/environment-setup" rel="noopener noreferrer"&gt;React Native official docs&lt;/a&gt; to create a new project &lt;code&gt;npx create-expo-app@latest&lt;/code&gt; generates a ton of boilerplate. It’s better to use the command they hand out at the official &lt;a href="https://docs.expo.dev/tutorial/create-your-first-app/" rel="noopener noreferrer"&gt;expo docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx create-expo-app PhotoApp --template blank&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Which brings to first source of ambiguity - which docs are more recent? But continuing with our experiment step 1 is probably all you need to get started. Next, running &lt;code&gt;npm run ios&lt;/code&gt; generates a nice UI&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%2F6d6xt4cqhe420zv95qaa.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%2F6d6xt4cqhe420zv95qaa.png" alt="Hello expo" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at the project directory in details shows that there are way fewer files and folders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── App.js
├── app.json
├── assets
├── babel.config.js
├── node_modules
├── package-lock.json
└── package.json

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

&lt;/div&gt;



&lt;p&gt;Now compare this with the older files and folders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── App.jsx
├── Gemfile
├── Gemfile.lock
├── README.md
├── __tests__
├── android
├── app.json
├── babel.config.js
├── index.js
├── ios
├── jest.config.js
├── metro.config.js
├── node_modules
├── package-lock.json
├── package.json
├── tsconfig.json
└── vendor

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Expo router
&lt;/h3&gt;

&lt;p&gt;Since I don’t think a lot has changed in the UI and state management. I’m going to jump directly at the real deal - the expo router. And with the real React Native developer spirit, just copy this one liner and install these bunch of dependencies and see the time fly&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx expo install expo-router react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, update the entry point to &lt;code&gt;expo-router&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;- "main": "expo/AppEntry.js"
+ "main": "expo-router/entry"

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

&lt;/div&gt;



&lt;p&gt;And add &lt;code&gt;scheme&lt;/code&gt; to &lt;code&gt;app.json&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;+ "scheme": "your-app-scheme"

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

&lt;/div&gt;



&lt;p&gt;And then again ignoring all the rest of the steps, since we don’t care about the web. Voila we have switched to expo-router!&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%2Fv7xwjwdxuw0b1b1h6rat.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%2Fv7xwjwdxuw0b1b1h6rat.png" alt="Hello expo-router" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation
&lt;/h3&gt;

&lt;p&gt;At first glance it seems the navigation is heavily inspired by next.js. Every screen is path based auto linked. So &lt;code&gt;index.js&lt;/code&gt; links to the &lt;code&gt;/&lt;/code&gt; path, aka the &lt;code&gt;Home&lt;/code&gt;. And for details screen we can use the parameterized path, like &lt;code&gt;/photos/[id]&lt;/code&gt; that would then magically navigate to the right &lt;code&gt;Details&lt;/code&gt; screen. And finally to describe the container we use the special component called &lt;code&gt;_layout.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For our app then the folder structure looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./app
├── _layout.js
├── index.js
└── photos
    └── [id].js
./components
├── photo_list.js
└── photo_tile.js

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

&lt;/div&gt;



&lt;p&gt;Since we want the navigation to be simple stack based, our &lt;code&gt;_layout.js&lt;/code&gt; looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function RootLayout() {
  return (
    &amp;lt;Stack&amp;gt;
      &amp;lt;Stack.Screen name="index" /&amp;gt;
      &amp;lt;Stack.Screen name="photos/[id]" /&amp;gt;
    &amp;lt;/Stack&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;Then within the &lt;code&gt;/index.js&lt;/code&gt; file is where we render a &lt;code&gt;Home&lt;/code&gt; screen that renders a &lt;code&gt;FlatList&lt;/code&gt; with each cell rendering a cell with an image and a title. And whenever the cell is tapped we invoke the router to take us to the &lt;code&gt;photo/[id]&lt;/code&gt; path&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;PhotoList
  photoList={photoList}
  onSelectPhoto={({ title, url }) =&amp;gt; {
    router.push({
      pathname: "/photos/[id]",
      params: { title, url },
    });
   }
  }
/&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;And later on in the &lt;code&gt;/photos/[id].js&lt;/code&gt; we can read the passed params and draw the UI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function DetailsScreen() {
  const { title, url } = useLocalSearchParams();
  return (
    &amp;lt;SafeAreaView style={styles.container}&amp;gt;
      &amp;lt;Image style={styles.image} source={
        {uri: url }
      } /&amp;gt;
      &amp;lt;Text style={styles.title}&amp;gt;{title}&amp;lt;/Text&amp;gt;
    &amp;lt;/SafeAreaView&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;And that’s all there is the learn. Expo looks neat. I like the path based navigation. Feels way better than other frameworks. And the best part is, like Steve Jobs would say, that it just works.&lt;/p&gt;

&lt;p&gt;I also like that they cleaned up the project a lot. But that said, I don’t know if I’m going to miss having access to the dedicated &lt;code&gt;ios&lt;/code&gt; and &lt;code&gt;android&lt;/code&gt; folders. What if for some reason I have to add some data to &lt;code&gt;Info.plist&lt;/code&gt; file or toggle some specific build setting. I hope there’s a easy way to make that work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR; I like expo and for my future React Native project I’m definitely going to use expo!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The code from this experiment is with the rest of the project at &lt;a href="https://github.com/chunkyguy/PhotoApp/tree/master/react-native/PhotoApp_expo" rel="noopener noreferrer"&gt;https://github.com/chunkyguy/PhotoApp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>reactnative</category>
      <category>expo</category>
    </item>
    <item>
      <title>UIScrollView and Autolayout</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Sun, 28 Apr 2024 10:24:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/uiscrollview-and-autolayout-4d0i</link>
      <guid>https://dev.to/chunkyguy/uiscrollview-and-autolayout-4d0i</guid>
      <description>&lt;p&gt;This a quick reference on how to make &lt;code&gt;UIScrollView&lt;/code&gt; work with autolayout&lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgflip.com/gif/8ymfqc" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OQk1CZ_w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i-download.imgflip.com/8ymfqc.gif" title="made at imgflip.com" width="360" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 1: Where the content has a fixed size
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ContentView: UIView {}

class ViewController: UIViewController {
  // ...
  override func viewDidLoad() {
    super.viewDidLoad()
    let contentVw = ContentView(frame: CGRect(
      x: 0, y: 0,
      width: 1024, 
      height: 1024
    ))
    let scrollVw = UIScrollView()

    VFL(view)
      .add(subview: scrollVw, name: "scrollVw")
      .applyConstraints(formats: [
        "H:|[scrollVw]|", "V:|[scrollVw]|"
      ])

    VFL(scrollVw)
      .addSubview(contentVw)

    scrollVw.backgroundColor = .red
    scrollVw.contentSize = contentVw.frame.size
  }
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solution 2: Where the content has an intrinsicContentSize
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ContentView: UIView {
  override var intrinsicContentSize: CGSize {
    CGSize(width: 1024, height: 1024)
  }
}

class ViewController: UIViewController {
  // ...
  override func viewDidLoad() {
    super.viewDidLoad()
    let contentVw = ContentView()
    let scrollVw = UIScrollView()

    VFL(view)
      .add(subview: scrollVw, name: "scrollVw")
      .applyConstraints(formats: [
        "H:|[scrollVw]|", "V:|[scrollVw]|"
      ])

    VFL(scrollVw)
      .add(subview: contentVw, name: "contentVw")
      .applyConstraints(formats: [
        "H:|[contentVw]|", "V:|[contentVw]|"
      ])
  }
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solution 3: Where the content has a flexible size
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ContentView: UIView {  
  weak var scrollVw: UIScrollView?

  override func layoutSubviews() {
    super.layoutSubviews()    
    scrollVw?.contentSize = bounds.size
  }
}

class ViewController: UIViewController {
  // ...
  override func viewDidLoad() {
    super.viewDidLoad()
    let contentVw = ContentView()
    let scrollVw = UIScrollView()

    VFL(view)
      .add(subview: scrollVw, name: "scrollVw")
      .applyConstraints(formats: [
        "H:|[scrollVw]|", "V:|[scrollVw]|"
      ])

    VFL(scrollVw)
      .add(subview: contentVw, name: "contentVw")
      .applyConstraints(formats: [
        "H:[contentVw]", "V:[contentVw]"
      ])

    // layout based on grandparent view    
    VFL(contentVw)
      .appendConstraints([
        contentVw.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        contentVw.trailingAnchor.constraint(
            equalTo: view.trailingAnchor,
            constant: 1024
        ),
        contentVw.topAnchor.constraint(equalTo: view.topAnchor),
        contentVw.bottomAnchor.constraint(
            equalTo: view.bottomAnchor,
            constant: 1024
        ),
      ])

    contentVw.scrollVw = scrollVw
  }
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  References:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/chunkyguy/introducing-vfl-576b-temp-slug-7174826"&gt;Introducing VFL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/library/archive/technotes/tn2154/_index.html" rel="noopener noreferrer"&gt;UIScrollView and Autolayout&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ios</category>
      <category>uikit</category>
      <category>layout</category>
    </item>
    <item>
      <title>Let's build a calculator</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Wed, 06 Mar 2024 21:21:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/lets-build-a-calculator-4c4b</link>
      <guid>https://dev.to/chunkyguy/lets-build-a-calculator-4c4b</guid>
      <description>&lt;p&gt;Let’s build a calculator!&lt;/p&gt;

&lt;p&gt;I saw this challenge on the &lt;a href="https://www.frontendmentor.io/challenges/calculator-app-9lteq5N29" rel="noopener noreferrer"&gt;frontendmentor website&lt;/a&gt; and the very next day while traveling to work I was trying to figure out how would I construct a calculator. I was thinking more like in terms of a real mechanical calculator rather than a software and then trying to figure out a circuit diagram.&lt;/p&gt;

&lt;p&gt;This is what I came up with:&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%2Fhd6iin9qzmta1spacdra.jpg" 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%2Fhd6iin9qzmta1spacdra.jpg" alt="Calculator circuit diagram" width="800" height="774"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me explain what is this mess.&lt;/p&gt;

&lt;p&gt;The data goes through 5 different stages, starting from &lt;strong&gt;I&lt;/strong&gt; nput that the user provides with the user interface and then depending on the type of the input it might go to the &lt;strong&gt;B&lt;/strong&gt; uffer or the &lt;strong&gt;O&lt;/strong&gt; perations, both of which are string types. But any change to the &lt;strong&gt;O&lt;/strong&gt; perations would trigger an update to the stored &lt;strong&gt;R&lt;/strong&gt; esult which is a number type and most likely a floating type. The result is computed using the last stored value and the value in the buffer. And finally, every input should eventually refresh the text on &lt;strong&gt;D&lt;/strong&gt; isplay.&lt;/p&gt;

&lt;p&gt;So for any numeric input we just update our buffer. The special case is with &lt;code&gt;0&lt;/code&gt; since we don’t want multiple leading &lt;code&gt;0s&lt;/code&gt;. Finally we concatenate the buffer and send the output string for display.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let buf = ["0"];

function handleInput(key) {
  switch (key) {
    // ....

    default:
      if (!"0123456789".includes(key)) return undefined;
      // if the only digit in buffer is a 0 then replace it with the input
      if (buf.length === 1 &amp;amp;&amp;amp; buf[0] === "0") {
        buf[0] = key;
      } else {
        buf.push(key);
      }
      return buf.join("");
  }
}

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

&lt;/div&gt;



&lt;p&gt;If the input is a command type (like &lt;code&gt;+-*/=&lt;/code&gt;) then we compute the result with the value in buffer and the last result and the last cached command. Remember that &lt;code&gt;+&lt;/code&gt; operation is actually performed after the user presses the &lt;code&gt;=&lt;/code&gt; button. For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Cache&lt;/th&gt;
&lt;th&gt;Display&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;=&lt;/td&gt;
&lt;td&gt;=&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Another caveat is to avoid keeping a default result value like say a &lt;code&gt;0&lt;/code&gt;. As this might not work for some operations like &lt;code&gt;*&lt;/code&gt; where then anything that multiplies with &lt;code&gt;0&lt;/code&gt; becomes &lt;code&gt;0&lt;/code&gt;. It’s better to just keep it &lt;code&gt;undefined&lt;/code&gt; initially.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let res = undefined;
let fn = undefined;

const add = (res, curr) =&amp;gt; res + curr;
const sub = (res, curr) =&amp;gt; res - curr;
const mul = (res, curr) =&amp;gt; res * curr;
const div = (res, curr) =&amp;gt; res / curr;
const eql = (res, curr) =&amp;gt; res;

function computeResult(next) {
  let curr = parseFloat(buf.join(""));
  curr = isNaN(curr) ? 0 : curr;

  if (res === undefined) {
    res = curr;
  } else {
    res = fn(res, curr);
  }

  fn = next;

  return res.toString();
}

function handleInput(key) {
  switch (key) {
    case "+":
      return computeResult(add);

    case "-":
      return computeResult(sub);

    case "*":
      return computeResult(mul);

    case "/":
      return computeResult(div);

    case "=":
      return computeResult(eql);

    // ...
  }
}

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

&lt;/div&gt;



&lt;p&gt;Then there is the special case of handling &lt;code&gt;.&lt;/code&gt; key. In this case we simply add the &lt;code&gt;.&lt;/code&gt; to the buffer is it isn’t there already.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function handleInput(key) {
  switch (key) {
    case ".":
      if (!buf.includes(".")) {
        buf.push(".");
      }
      return buf.join("");

    // ...
  }
}

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

&lt;/div&gt;



&lt;p&gt;Similar buffer manipulations can be performed for other commands like when user presses the &lt;code&gt;DEL&lt;/code&gt; button which just pops the buffer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function handleInput(key) {
  switch (key) {
    case "Backspace":
      buf.pop();
      if (buf.length === 0) {
        buf = ["0"];
      }
      return buf.join("");

    // ...
  }
}

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

&lt;/div&gt;



&lt;p&gt;You must have realized that although internally we keep the computed result in &lt;code&gt;res&lt;/code&gt; as &lt;code&gt;number&lt;/code&gt; but we return back a &lt;code&gt;string&lt;/code&gt;. This is to avoid dealing with the complexities that comes when dealing with floating point numbers. The drawing output to display is then simply the matter of string manipulation, and in my personal case since my calculator can not fit more than 13 digits I can simply not display more than 13 chars.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const outFld = document.querySelector("#output");

function draw(text) {
  if (text === undefined) return;
  outFld.innerHTML = text.slice(0, 13);
}

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

&lt;/div&gt;



&lt;p&gt;If you want you can play with the final result &lt;a href="https://whackylabs.com/frontendmentor/calculator-app/" rel="noopener noreferrer"&gt;here&lt;/a&gt; or go play with the &lt;a href="https://github.com/chunkyguy/frontendmentor/tree/main/calculator-app" rel="noopener noreferrer"&gt;source code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>calculator</category>
      <category>frontendmentor</category>
    </item>
    <item>
      <title>Hello MAUI</title>
      <dc:creator>Sidharth Juyal</dc:creator>
      <pubDate>Tue, 23 Jan 2024 23:30:00 +0000</pubDate>
      <link>https://dev.to/chunkyguy/hello-maui-2nme</link>
      <guid>https://dev.to/chunkyguy/hello-maui-2nme</guid>
      <description>&lt;p&gt;So another cross platform tool that I’ve been trying to poke for a very very long time now is .NET MAUI. Since I love making games with Unity and C# so I’ve always wondered what would it feel like to also make apps with C#. Also I’ve heard good things about .NET. So yes, lets dive into it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgflip.com/i/8ynkqq" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8eJLerxy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgflip.com/8ynkqq.jpg" title="made at imgflip.com" width="622" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up
&lt;/h3&gt;

&lt;p&gt;The first struggle getting started with MAUI is that according to the &lt;a href="https://dotnet.microsoft.com/en-us/learn/maui/first-app-tutorial/install" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; the first step is to &lt;strong&gt;Download and install Visual Studio 2022 for Mac&lt;/strong&gt; but also &lt;a href="https://9to5mac.com/2023/08/30/microsoft-visual-studio-mac-discontinued/" rel="noopener noreferrer"&gt;recently they announced&lt;/a&gt; that Visual Studio for Mac is going away soon. The recommended route for the upcoming future is to use the Visual Studio Code with the official extensions, so this is what I’m also going to try.&lt;/p&gt;

&lt;p&gt;So let’s install:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-maui" rel="noopener noreferrer"&gt;vscode extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/install/macOS" rel="noopener noreferrer"&gt;dotnet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dotnet workload install maui&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next up, create the project using whatever &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/get-started/first-app?pivots=devices-ios&amp;amp;view=net-maui-8.0&amp;amp;tabs=visual-studio-code" rel="noopener noreferrer"&gt;they tell you to&lt;/a&gt;. And then after spending a weekend updating and installing the ‘correct’ version of macOS, Xcode, Simulator runtimes and whatnot we finally have our MAUI project building and running!&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%2Fnsoz3d4mjyt6ud85qb3x.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%2Fnsoz3d4mjyt6ud85qb3x.png" alt="Hello MAUI" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maybe using Android as the base development target would had been easier, but who know&lt;/p&gt;

&lt;h3&gt;
  
  
  Let’s make the app
&lt;/h3&gt;

&lt;p&gt;So the next struggle is to find good resources on learning .NET MAUI. There are very limited resources out there. From the official docs I was able to find a good tutorial on getting started with MAUI. The &lt;a href="https://www.youtube.com/watch?v=DuNLR_NJv8U" rel="noopener noreferrer"&gt;workshop&lt;/a&gt; is about building an app called &lt;strong&gt;Monkey Finder&lt;/strong&gt;. Sounds great!&lt;/p&gt;

&lt;p&gt;So again looking at our json data from &lt;a href="https://jsonplaceholder.typicode.com/photos" rel="noopener noreferrer"&gt;jsonplaceholder/photos&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "albumId": 1,
    "id": 1,
    "title": "accusamus beatae ad facilis cum similique qui sunt",
    "url": "https://via.placeholder.com/600/92c952",
    "thumbnailUrl": "https://via.placeholder.com/150/92c952"
  },
]

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

&lt;/div&gt;



&lt;p&gt;the first thing we need is a model object, conveniently called &lt;code&gt;Photo&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;public class Photo
{
    public int albumId { get; set; }
    public int id { get; set; }
    public string title { get; set; }
    public string url { get; set; }
    public string thumbnailUrl { get; set; }
}

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

&lt;/div&gt;



&lt;p&gt;Next, we want to render this object in an element. With MAUI or .NET development in general it seems to be the standard practice to group classes under an various namespace grouping similar functionalities, like &lt;code&gt;Models&lt;/code&gt; and &lt;code&gt;Views&lt;/code&gt;. So the &lt;code&gt;Photo&lt;/code&gt; type above would be under &lt;code&gt;namespace PhotoApp.Models&lt;/code&gt; and all the views would be under &lt;code&gt;namespace PhotoApp.Views&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then in the xaml files the namespaces can be imported as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xmlns:views="clr-namespace:PhotoApp.Views"

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

&lt;/div&gt;



&lt;p&gt;Another pattern is to use a &lt;code&gt;local&lt;/code&gt; namespace for the entire App code and no more worrying about namespaces anymore.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xmlns:local="clr-namespace:PhotoApp"

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

&lt;/div&gt;



&lt;p&gt;To be honest I can’t make up my mind. I’ll try them both out and see whatever feels good.&lt;/p&gt;

&lt;p&gt;The UI API is like any other old school xml based api. So for a list of items with each item having a image and text elements vertically laid out we can use a &lt;code&gt;CollectionView&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;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;
&amp;lt;ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:models="clr-namespace:PhotoApp.Models"
             x:Class="PhotoApp.Views.MainPage"&amp;gt;
    &amp;lt;CollectionView&amp;gt;
        &amp;lt;CollectionView.ItemsSource&amp;gt;
            &amp;lt;x:Array Type="{x:Type models:Photo}"&amp;gt;
                &amp;lt;models:Photo
                    albumId="1"
                    id="1"
                    title="accusamus beatae ad facilis cum similique qui sunt"
                    url="https://via.placeholder.com/600/92c952"
                    thumbnailUrl="https://via.placeholder.com/150/92c952" /&amp;gt;
                &amp;lt;models:Photo 
                    albumId="1"
                    id="2"
                    title="reprehenderit est deserunt velit ipsam"
                    url="https://via.placeholder.com/600/771796"
                    thumbnailUrl="https://via.placeholder.com/150/771796" /&amp;gt;
                &amp;lt;models:Photo 
                    albumId="1"
                    id="3"
                    title="officia porro iure quia iusto qui ipsa ut modi"
                    url="https://via.placeholder.com/600/24f355"
                    thumbnailUrl="https://via.placeholder.com/150/24f355"/&amp;gt;
            &amp;lt;/x:Array&amp;gt;
        &amp;lt;/CollectionView.ItemsSource&amp;gt;
        &amp;lt;CollectionView.ItemTemplate&amp;gt;
            &amp;lt;DataTemplate x:DataType="models:Photo"&amp;gt;
                &amp;lt;VerticalStackLayout Padding="8"&amp;gt;
                    &amp;lt;Image Source="{Binding thumbnailUrl}" /&amp;gt;
                    &amp;lt;Label 
                        Text="{Binding title}" 
                        HorizontalOptions = "LayoutOptions.CenterAndExpand" /&amp;gt;
                &amp;lt;/VerticalStackLayout&amp;gt;
            &amp;lt;/DataTemplate&amp;gt;
        &amp;lt;/CollectionView.ItemTemplate&amp;gt;
    &amp;lt;/CollectionView&amp;gt;
&amp;lt;/ContentPage&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;The interesting bit is that while prototyping we can hardcode that data right within xaml using &lt;code&gt;CollectionView.ItemsSource&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each element within the collection view needs a &lt;code&gt;ItemTemplate&lt;/code&gt; and then within the &lt;code&gt;ItemTemplate&lt;/code&gt; we can define the sort of function per data type with &lt;code&gt;DataTemplate&lt;/code&gt; with the argument being passed in as &lt;code&gt;Binding&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Mentally I think of this in code as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CollectionView(
    itemsSource = Photo[] { ... },
    itemTemplate = {
        dataTemplate = (binding: Photo) =&amp;gt; View { ... }
    }
)

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

&lt;/div&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%2Fzt0ep7zuqi5mcwqvc8kx.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%2Fzt0ep7zuqi5mcwqvc8kx.png" alt="MainPage" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And similarly for the details page we can do something like:&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;?xml version="1.0" encoding="utf-8" ?&amp;gt;
&amp;lt;ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:models="clr-namespace:PhotoApp.Models"
             x:Class="PhotoApp.Views.DetailsPage"&amp;gt;
    &amp;lt;CollectionView&amp;gt;
        &amp;lt;CollectionView.ItemsSource&amp;gt;
            &amp;lt;x:Array Type="{x:Type models:Photo}"&amp;gt;
                &amp;lt;models:Photo
                    albumId="1"
                    id="1"
                    title="accusamus beatae ad facilis cum similique qui sunt"
                    url="https://via.placeholder.com/600/92c952"
                    thumbnailUrl="https://via.placeholder.com/150/92c952" /&amp;gt;
            &amp;lt;/x:Array&amp;gt;
        &amp;lt;/CollectionView.ItemsSource&amp;gt;
        &amp;lt;CollectionView.ItemTemplate&amp;gt;
            &amp;lt;DataTemplate x:DataType="models:Photo"&amp;gt;
                &amp;lt;VerticalStackLayout Padding="8"&amp;gt;
                    &amp;lt;Image Source="{Binding url}" /&amp;gt;
                    &amp;lt;Label 
                        Text="{Binding title}" 
                        HorizontalOptions = "LayoutOptions.CenterAndExpand" /&amp;gt;
                &amp;lt;/VerticalStackLayout&amp;gt;
            &amp;lt;/DataTemplate&amp;gt;
        &amp;lt;/CollectionView.ItemTemplate&amp;gt;
    &amp;lt;/CollectionView&amp;gt;
&amp;lt;/ContentPage&amp;gt;

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

&lt;/div&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%2Fu0bs1dtq7x2zgood2bsu.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%2Fu0bs1dtq7x2zgood2bsu.png" alt="DetailsPage" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the cool things that I like about xaml is that we can use the shorthand or the long version of a tag. So these two are identical:&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;Label FontSize="22" /&amp;gt;

&amp;lt;Label&amp;gt;
    &amp;lt;Label.FontSize&amp;gt;
        22
    &amp;lt;/Label.FontSize&amp;gt;
&amp;lt;/Label&amp;gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Navigation
&lt;/h3&gt;

&lt;p&gt;So how about adding a transition between the screens. To achieve this there are quite a number of steps.&lt;/p&gt;

&lt;p&gt;First we need to register a route. This is pretty simple in MAUI. There’s actually a pretty nice convenience way to avoid hardcoding &lt;code&gt;"DetailsPage"&lt;/code&gt; by using the &lt;code&gt;nameof&lt;/code&gt; expression.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
        Routing.RegisterRoute(nameof(DetailsPage), typeof(DetailsPage));
    }
}

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

&lt;/div&gt;



&lt;p&gt;Next sticking with the MVC pattern we need to create a &lt;code&gt;MainController&lt;/code&gt; class for our &lt;code&gt;MainPage&lt;/code&gt;. The controller only has a single command which is to invoke the &lt;code&gt;"DetailsPage"&lt;/code&gt; route&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public partial class MainController : ObservableObject
{
    [RelayCommand]
    async Task Tap()
    {
        await Shell.Current.GoToAsync(nameof(DetailsPage));
    }
}

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

&lt;/div&gt;



&lt;p&gt;And next we need to inject the &lt;code&gt;MainController&lt;/code&gt; as an dependency to the &lt;code&gt;MainPage&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;public partial class MainPage : ContentPage
{
    public MainPage(MainController controller)
    {
        InitializeComponent();
        BindingContext = controller;
    }
}

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

&lt;/div&gt;



&lt;p&gt;We need to set the &lt;code&gt;BindingContext&lt;/code&gt; to the controller to have this available in the associated xaml file. And that is where we can add a &lt;code&gt;TapGestureRecognizer&lt;/code&gt; to the &lt;code&gt;Image&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;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;
&amp;lt;ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:models="clr-namespace:PhotoApp.Models"
             xmlns:ctrls="clr-namespace:PhotoApp.Controllers"
             x:Class="PhotoApp.Views.MainPage"
             x:DataType="PhotoApp.Controllers.MainController"&amp;gt;
    &amp;lt;CollectionView&amp;gt;
        &amp;lt;CollectionView.ItemsSource&amp;gt; ... &amp;lt;/CollectionView.ItemsSource&amp;gt;
        &amp;lt;CollectionView.ItemTemplate&amp;gt;
            &amp;lt;DataTemplate x:DataType="models:Photo"&amp;gt;
                &amp;lt;VerticalStackLayout Padding="8"&amp;gt;
                    &amp;lt;Image Source="{Binding thumbnailUrl}"&amp;gt;
                        &amp;lt;Image.GestureRecognizers&amp;gt;
                        &amp;lt;TapGestureRecognizer 
                            Command="{Binding Source={RelativeSource AncestorType={x:Type ctrls:MainController}}, Path=TapCommand}" /&amp;gt;
                        &amp;lt;/Image.GestureRecognizers&amp;gt;
                    &amp;lt;/Image&amp;gt;
                    &amp;lt;Label 
                        Text="{Binding title}" 
                        HorizontalOptions = "LayoutOptions.CenterAndExpand" /&amp;gt;
                &amp;lt;/VerticalStackLayout&amp;gt;
            &amp;lt;/DataTemplate&amp;gt;
        &amp;lt;/CollectionView.ItemTemplate&amp;gt;
    &amp;lt;/CollectionView&amp;gt;
&amp;lt;/ContentPage&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;The final missing piece is to make sure that the &lt;code&gt;MainController&lt;/code&gt; is injected into our &lt;code&gt;MainPage&lt;/code&gt; when constructed. This can be done in the &lt;code&gt;MauiProgram&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;public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        // ...
        builder.Services.AddSingleton&amp;lt;MainPage&amp;gt;();
        builder.Services.AddSingleton&amp;lt;MainController&amp;gt;();
        // ...
        return builder.Build();
    }
}

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

&lt;/div&gt;



&lt;p&gt;This pattern prepares the builder factory to provide instances as a singleton or as transient whenever requested.&lt;/p&gt;

&lt;p&gt;And with this in place we can finally go from &lt;code&gt;MainPage&lt;/code&gt; to &lt;code&gt;DetailsPage&lt;/code&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%2Fulfy5mecm3rv7704jxux.gif" 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%2Fulfy5mecm3rv7704jxux.gif" alt="Navigation" width="296" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To pop back we need to create and bind a &lt;code&gt;DetailsController&lt;/code&gt; with &lt;code&gt;DetailsPage&lt;/code&gt; and navigate back to parent with &lt;code&gt;..&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;public partial class DetailsController : ObservableObject
{
    [RelayCommand]
    async Task GoBack()
    {
        await Shell.Current.GoToAsync("..", true);
    }
}


// MainProgram.cs
builder.Services.AddTransient&amp;lt;DetailsPage&amp;gt;();
builder.Services.AddTransient&amp;lt;DetailsController&amp;gt;();


&amp;lt;!-- DetailsPage.xaml --&amp;gt;
&amp;lt;Shell.BackButtonBehavior&amp;gt;
    &amp;lt;BackButtonBehavior Command="{Binding GoBackCommand}" TextOverride="Back" /&amp;gt;   
&amp;lt;/Shell.BackButtonBehavior&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;For some reason I’m unable to have the “Back” text on the navbar. But anyways when I tap on the empty space where the back button should’ve been otherwise I’m able to pop back. Is this a bug or feature it’s too early for me to say.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetching data
&lt;/h3&gt;

&lt;p&gt;Next up is fetching data from the network. One easy way to get started is to have pull to refresh. This can be achieved by adding &lt;code&gt;RefreshView&lt;/code&gt; to the &lt;code&gt;MainPage&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;&amp;lt;RefreshView 
    IsRefreshing="{Binding IsRefreshing}"
    Command="{Binding GetPhotosCommand}"&amp;gt;
    &amp;lt;CollectionView 
        ItemsSource="{Binding Photos}"
        SelectionMode="None" &amp;gt;
        &amp;lt;CollectionView.ItemTemplate&amp;gt;
            &amp;lt;DataTemplate x:DataType="local:Photo"&amp;gt;
                &amp;lt;VerticalStackLayout Padding="8"&amp;gt;
                    &amp;lt;Image Source="{Binding thumbnailUrl}" /&amp;gt;
                    &amp;lt;Label 
                        Text="{Binding title}" 
                        HorizontalOptions = "LayoutOptions.CenterAndExpand" /&amp;gt;
                &amp;lt;/VerticalStackLayout&amp;gt;
            &amp;lt;/DataTemplate&amp;gt;
        &amp;lt;/CollectionView.ItemTemplate&amp;gt;
    &amp;lt;/CollectionView&amp;gt;
&amp;lt;/RefreshView&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Notice that we are now loading the data from a &lt;code&gt;Photos&lt;/code&gt; collection from the context, and similarly we are sending the &lt;code&gt;GetPhotosCommand&lt;/code&gt; to the context whenever the pull-to-refresh is triggered.&lt;/p&gt;

&lt;p&gt;Next step is to actually implement the &lt;code&gt;GetPhotosCommand&lt;/code&gt; in &lt;code&gt;BindingContext&lt;/code&gt; aka the &lt;code&gt;MainController&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;public partial class MainController : ObservableObject
{
    public ObservableCollection&amp;lt;Photo&amp;gt; Photos { get; } = new();
    PhotoService photoService;

    [ObservableProperty]
    bool isRefreshing;

    [ObservableProperty]
    string title;

    public MainController(PhotoService photoService)
    {
        this.photoService = photoService;
        Title = "Photos";
    }

    [RelayCommand]
    async Task GetPhotos()
    {
        try
        {
            var photos = await photoService.GetPhotos();
            Photos.Clear();
            // The default photos is a list of 5000 elements .. so we only load first 100
            for (int i = 0; i &amp;lt; photos.Count &amp;amp;&amp;amp; i &amp;lt; 100; i++)
                Photos.Add(photos[i]);
        }
        catch (System.Exception ex)
        {
            // TODO: show error UI

        }
        finally
        {
            IsRefreshing = false;
        }
    }
}

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

&lt;/div&gt;



&lt;p&gt;Due to the .NET magic, &lt;code&gt;GetPhotosCommand&lt;/code&gt; is mapped to &lt;code&gt;GetPhotos&lt;/code&gt; method because it is annotated with &lt;code&gt;RelayCommand&lt;/code&gt;. And thanks to the magic of &lt;code&gt;ObservableCollection&lt;/code&gt; the UI will refresh automatically whenever the collection is updated. But this could have some performance implication, so I tried to only load a few photos.&lt;/p&gt;

&lt;p&gt;The final missing piece is the &lt;code&gt;PhotoService&lt;/code&gt;, which is also probably the least surprising, just some good old data fetching and json parsing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class PhotoService
{
    HttpClient httpClient;

    public PhotoService()
    {
        httpClient = new HttpClient();
    }

    public async Task&amp;lt;List&amp;lt;Photo&amp;gt;&amp;gt; GetPhotos()
    {
        var response = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/photos");
        if (response.IsSuccessStatusCode)
        {
            return await response.Content.ReadFromJsonAsync(PhotoContext.Default.ListPhoto);
        }
        return new List&amp;lt;Photo&amp;gt;();
    }
}

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PhotoContext&lt;/code&gt; is how cool kids provide a &lt;code&gt;List&amp;lt;Photo&amp;gt;&lt;/code&gt; type&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[JsonSerializable(typeof(List&amp;lt;Photo&amp;gt;))]
internal sealed partial class PhotoContext : JsonSerializerContext {}

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

&lt;/div&gt;



&lt;p&gt;And so we finally can get the data from network&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%2Fma331ib2f1g407leitla.gif" 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%2Fma331ib2f1g407leitla.gif" alt="Network" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing data between screens
&lt;/h3&gt;

&lt;p&gt;And with that the last remaining item on the list is to pass the data between pages. So to keep things simple if we were to have the &lt;code&gt;DetailsPage&lt;/code&gt; also make use of &lt;code&gt;CollectionView&lt;/code&gt; but rather have an array of one element as data source, our &lt;code&gt;DetailsController&lt;/code&gt; would look almost identical to &lt;code&gt;MainController&lt;/code&gt; except that we also need to implement the &lt;code&gt;IQueryAttributable&lt;/code&gt; to support receiving of data that we would send from the &lt;code&gt;MainPage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In our case we would be passing a &lt;code&gt;Dictionary&lt;/code&gt; with single element in it as &lt;code&gt;Photo&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;public partial class DetailsController : ObservableObject, IQueryAttributable
{
    public ObservableCollection&amp;lt;Photo&amp;gt; Photos { get; } = new();

    [ObservableProperty]
    string title;

    public DetailsController()
    {
        Title = "Details";
    }

    public void ApplyQueryAttributes(IDictionary&amp;lt;string, object&amp;gt; query)
    {
        var photo = query["Photo"] as Photo;
        Photos.Add(photo);
    }

    //...
}

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

&lt;/div&gt;



&lt;p&gt;Next in the &lt;code&gt;MainController&lt;/code&gt; we need to update the &lt;code&gt;TapCommand&lt;/code&gt; to take in a &lt;code&gt;Photo&lt;/code&gt; as parameter that can be forwarded to the &lt;code&gt;DetailsPage&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;public partial class MainController : ObservableObject
{
    [RelayCommand]
    async Task Tap(Photo photo)
    {
        var navigationParameter = new Dictionary&amp;lt;string, object&amp;gt; { { "Photo", photo } };
        await Shell.Current.GoToAsync(nameof(DetailsPage), navigationParameter);
    }

    // ...
}

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

&lt;/div&gt;



&lt;p&gt;And finally, from the &lt;code&gt;MainPage&lt;/code&gt; UI we need to fill in the &lt;code&gt;Photo&lt;/code&gt; as &lt;code&gt;CommandParameter&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;&amp;lt;DataTemplate x:DataType="local:Photo"&amp;gt;
    &amp;lt;VerticalStackLayout&amp;gt;
        &amp;lt;Image Source="{Binding thumbnailUrl}"&amp;gt;
            &amp;lt;Image.GestureRecognizers&amp;gt;
            &amp;lt;TapGestureRecognizer 
Command="{Binding Source={RelativeSource AncestorType={x:Type local:MainController}}, Path=TapCommand}" 
CommandParameter="{Binding .}" /&amp;gt;
            &amp;lt;/Image.GestureRecognizers&amp;gt;
        &amp;lt;/Image&amp;gt;
    &amp;lt;/VerticalStackLayout&amp;gt;
&amp;lt;/DataTemplate&amp;gt;

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

&lt;/div&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%2Fio6si7vusdy7cwuz0nyo.gif" 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%2Fio6si7vusdy7cwuz0nyo.gif" alt="Passing Data" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;This brings me to the end of this experiment for now. So how do I feel about MAUI? I think I’m sort of relieved that this experiment is done for now. I feel that MAUI needs a &lt;em&gt;lot&lt;/em&gt; of work before it can actually be used for real. There are way too many bugs currently. And on macOS, which I guess is the main machine for most of the mobile devs, working on MAUI with Visual Studio Code was very painful. But I hope it would improve.&lt;/p&gt;

&lt;p&gt;Said that, I do like the idea of having a nice language like C# for making apps. MAUI has all the potential to be the best cross-platform mobile development tool out there. Also building on top of .NET and other battle tested Microsoft tech sounds very promising and I was able to find a lot of help online for any question I had. So I would definitely be keeping an eye out for MAUI.&lt;/p&gt;

&lt;p&gt;Next I would probably try MAUI from a Windows machine with Visual Studio, you know the way it’s supposed to be, and see if that actually improves the developer experience.&lt;/p&gt;

&lt;p&gt;The code is available at &lt;a href="https://github.com/chunkyguy/PhotoApp/tree/master/maui" rel="noopener noreferrer"&gt;github.com/chunkyguy/PhotoApp&lt;/a&gt; like always.&lt;/p&gt;

</description>
      <category>maui</category>
      <category>ios</category>
      <category>android</category>
    </item>
  </channel>
</rss>
