<?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>Forem: Youngho Andrew Chaa</title>
    <description>The latest articles on Forem by Youngho Andrew Chaa (@andrewchaa).</description>
    <link>https://forem.com/andrewchaa</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%2F429117%2F881daf9d-dcf0-439f-b762-9587900ffc89.jpeg</url>
      <title>Forem: Youngho Andrew Chaa</title>
      <link>https://forem.com/andrewchaa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/andrewchaa"/>
    <language>en</language>
    <item>
      <title>The Secret Language of Data: Vectors and Cosine Similarity</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Wed, 10 Dec 2025 16:13:16 +0000</pubDate>
      <link>https://forem.com/andrewchaa/the-secret-language-of-data-vectors-and-cosine-similarity-4e2a</link>
      <guid>https://forem.com/andrewchaa/the-secret-language-of-data-vectors-and-cosine-similarity-4e2a</guid>
      <description>&lt;p&gt;Ever wonder how Netflix knows what movies you'll like, or how Google finds exactly what you're searching for even if you just type a few words? It's math, unfortunately 😂. And at the heart of it lies a powerful concept called &lt;strong&gt;Cosine Similarity&lt;/strong&gt;, built upon the idea of &lt;strong&gt;vectors&lt;/strong&gt; and the humble &lt;strong&gt;cosine&lt;/strong&gt; function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1: What is a Vector? (Your Data's Direction and Distance)
&lt;/h3&gt;

&lt;p&gt;Imagine you're giving directions to a friend: "Walk 5 blocks North." This simple instruction contains two crucial pieces of information:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Direction: North&lt;/li&gt;
&lt;li&gt; Magnitude (or Distance): 5 blocks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the world of math and data science, anything that has both a &lt;strong&gt;direction&lt;/strong&gt; and a &lt;strong&gt;magnitude&lt;/strong&gt; is called a &lt;strong&gt;vector&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Vectors in Real Life:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;A plane flying 500 mph East.&lt;/li&gt;
&lt;li&gt;The wind blowing 15 mph from the Northwest.&lt;/li&gt;
&lt;li&gt;The force you use to kick a soccer ball.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We draw vectors as arrows, where the arrow's length shows its magnitude, and its point shows its direction. In computers, we represent them as lists of numbers: &lt;code&gt;[3, 4]&lt;/code&gt; might mean "go 3 steps right, then 4 steps up."&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2: The Cosine Function (The Shadow Ruler)
&lt;/h3&gt;

&lt;p&gt;Next, let's talk about &lt;strong&gt;Cosine&lt;/strong&gt;. This is one of three main tools (Sine, Cosine, Tangent) we use to measure right-angled triangles (triangles with a perfect 90-degree corner).&lt;/p&gt;

&lt;p&gt;Imagine a ladder leaning against a wall. The ladder itself is the &lt;strong&gt;Hypotenuse&lt;/strong&gt; (the longest side). The ground it touches is the &lt;strong&gt;Adjacent&lt;/strong&gt; side (it's "next to" the angle the ladder makes with the ground). The wall it reaches is the &lt;strong&gt;Opposite&lt;/strong&gt; side (it's "opposite" the angle).&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Cosine rule (CAH)&lt;/strong&gt; tells us:&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%2Fn0fphlczhpvdbebjyg7x.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%2Fn0fphlczhpvdbebjyg7x.png" alt=" " width="746" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Think of it like this: The cosine tells you how much of the ladder's length is stretched out along the ground (its "shadow").&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the ladder is almost flat (small angle), its shadow is long ($\cos(\theta)$ is close to 1).&lt;/li&gt;
&lt;li&gt;If the ladder is almost straight up (large angle), its shadow is short ($\cos(\theta)$ is close to 0).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 3: Putting It Together – Cosine Similarity for Documents!
&lt;/h3&gt;

&lt;p&gt;Now, for the exciting part: how do vectors and cosine help us compare documents or anything else?&lt;/p&gt;

&lt;p&gt;Imagine every document, sentence, or even a user's movie preferences, is turned into a &lt;strong&gt;vector&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  📝 Document Vectors Explained:
&lt;/h4&gt;

&lt;p&gt;Let's take two sentences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sentence A: "Apple is sweet."&lt;/li&gt;
&lt;li&gt;Sentence B: "Banana is sweet."&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vocabulary:&lt;/strong&gt; First, we make a list of all unique, important words across all our sentences: &lt;code&gt;[Apple, Banana, sweet]&lt;/code&gt;. This list defines our &lt;strong&gt;dimensions&lt;/strong&gt;. (Our vector will have 3 dimensions, not because each sentence has 3 words, but because our &lt;em&gt;vocabulary&lt;/em&gt; has 3 unique words!)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Vector Creation (Term Frequency):&lt;/strong&gt; We then count how often each word appears in each sentence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vector A for "Apple is sweet": &lt;code&gt;[1, 0, 1]&lt;/code&gt; (1 Apple, 0 Banana, 1 sweet)&lt;/li&gt;
&lt;li&gt;Vector B for "Banana is sweet": &lt;code&gt;[0, 1, 1]&lt;/code&gt; (0 Apple, 1 Banana, 1 sweet)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  The "Shadow" of Document Similarity:
&lt;/h4&gt;

&lt;p&gt;Now, we want to know how similar these two vectors (sentences) are. Cosine Similarity asks: &lt;strong&gt;"Are these two vectors pointing in roughly the same direction?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The formula is:&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%2Fy5lbfchuja9ai356r45c.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%2Fy5lbfchuja9ai356r45c.png" alt=" " width="514" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's break down the parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A . B (The Dot Product):&lt;/strong&gt; This calculates the "overlap" or "alignment" between the two vectors. It multiplies the counts for each word and adds them up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For our example: &lt;code&gt;(1*0) + (0*1) + (1*1) = 0 + 0 + 1 = 1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The dot product effectively finds out how much of Document A's "shadow" falls directly onto Document B's "path." If both documents frequently use a common word, this number goes up!&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;||A|| ||B|| (The Magnitudes):&lt;/strong&gt; These are the "lengths" of our document vectors. A longer document will have a larger magnitude.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For our example: &lt;code&gt;||A|| = sqrt(1^2 + 0^2 + 1^2) = sqrt(2)&lt;/code&gt; and &lt;code&gt;||B|| = sqrt(0^2 + 1^2 + 1^2) = sqrt(2)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Putting it all together:&lt;/p&gt;&lt;/li&gt;

&lt;/ul&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%2Flejsrxmjj174j9zfpv0t.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%2Flejsrxmjj174j9zfpv0t.png" alt=" " width="718" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What does 0.5 mean?
&lt;/h4&gt;

&lt;p&gt;A score of &lt;strong&gt;0.5&lt;/strong&gt; tells us that "Apple is sweet" and "Banana is sweet" have a moderate similarity. They share the concept of "sweet" but differ on the main subject.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A score of &lt;strong&gt;1&lt;/strong&gt; means they are identical in direction (talking about the exact same thing).&lt;/li&gt;
&lt;li&gt;A score of &lt;strong&gt;0&lt;/strong&gt; means they are completely unrelated in direction (talking about totally different things).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Power of Cosine Similarity
&lt;/h3&gt;

&lt;p&gt;The real genius of Cosine Similarity is that it focuses purely on the &lt;strong&gt;direction (topic)&lt;/strong&gt; of the vectors, not their &lt;strong&gt;magnitude (length)&lt;/strong&gt;. A short document and a long document can be highly similar if they both discuss the same topic in the same proportions. This is incredibly useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search Engines:&lt;/strong&gt; Finding documents that are topically relevant to your query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recommendation Systems:&lt;/strong&gt; Suggesting movies, products, or articles similar to what you already like.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plagiarism Detection:&lt;/strong&gt; Identifying documents with similar content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, the next time you get a perfect search result or a spot-on recommendation, remember the silent power of vectors and their angular dance, measured by the humble cosine!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Decoding the 'Invalid Color Space' Error: A Dive into an FFmpeg 7 Bug</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Wed, 08 Oct 2025 09:08:42 +0000</pubDate>
      <link>https://forem.com/andrewchaa/decoding-the-invalid-color-space-error-a-dive-into-an-ffmpeg-7-bug-2636</link>
      <guid>https://forem.com/andrewchaa/decoding-the-invalid-color-space-error-a-dive-into-an-ffmpeg-7-bug-2636</guid>
      <description>&lt;p&gt;It's a scenario every developer knows: a critical process, running smoothly for months, suddenly starts failing in production. No recent code changes on our side, no infrastructure tweaks. This is the story of how we chased down a mysterious &lt;code&gt;ffmpeg&lt;/code&gt; error and implemented a robust fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unexpected Error
&lt;/h2&gt;

&lt;p&gt;Recently, our video processing pipeline began throwing a peculiar error for some user-uploaded videos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream #0:0[0x1](eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, reserved/reserved/smpte170m, progressive), 398x720, 1060 kb/s, 27.37 fps, 29.83 tbr, 90k tbn (default)
      Metadata:
        creation_time   : 2025-10-08T08:31:00.000000Z
        handler_name    : VideoHandle
        vendor_id       : [0][0][0][0]
[graph -1 input from stream 0:0 @ 0x7f505c004880] Invalid color space
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key phrase here is &lt;code&gt;Invalid color space&lt;/code&gt;. This was puzzling because our &lt;code&gt;ffmpeg&lt;/code&gt; commands for image extraction hadn't changed. The error suggested that &lt;code&gt;ffmpeg&lt;/code&gt; couldn't determine the correct color mapping for the video, but why now?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Root Cause: A Bug in FFmpeg 7
&lt;/h2&gt;

&lt;p&gt;After some investigation, I traced the problem to a recent update of &lt;code&gt;ffmpeg&lt;/code&gt; on our production servers to version 7. A search through the &lt;code&gt;ffmpeg&lt;/code&gt; bug tracker led us to this ticket: &lt;a href="https://fftrac-bg.ffmpeg.org/ticket/11020" rel="noopener noreferrer"&gt;#11020 - Invalid color space error&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The bug report confirmed our suspicions. A change in &lt;code&gt;ffmpeg&lt;/code&gt; version 7 meant that it no longer defaulted to a standard color space (like BT.709) when a video's metadata was missing or ambiguous. While technically a move towards stricter compliance, this change broke compatibility with many existing videos that don't explicitly declare their color space.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: Explicitly Setting Metadata
&lt;/h2&gt;

&lt;p&gt;Since we couldn't ask all our users to re-encode their videos, we needed to adapt our code. The solution was to explicitly tell &lt;code&gt;ffmpeg&lt;/code&gt; which color space to assume for these videos.&lt;/p&gt;

&lt;p&gt;I achieved this by using &lt;code&gt;ffmpeg&lt;/code&gt;'s bitstream filter (&lt;code&gt;-bsf:v&lt;/code&gt;) to inject the necessary metadata during processing. Specifically, I added the argument &lt;code&gt;h264_metadata=matrix_coefficients=1&lt;/code&gt;. This tells &lt;code&gt;ffmpeg&lt;/code&gt; to treat the H.264 video stream as having color matrix coefficients corresponding to the BT.709 standard, a common choice for HD video, resolving the ambiguity.&lt;/p&gt;

&lt;p&gt;However, there's a catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Fix Robust: A Codec-Specific Solution
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;h264_metadata&lt;/code&gt; filter is, as the name implies, only for H.264 videos. Applying it to a video encoded with a different codec (like VP9 or HEVC) would cause &lt;code&gt;ffmpeg&lt;/code&gt; to fail with a different error. To prevent this, we first need to identify the video's codec.&lt;/p&gt;

&lt;p&gt;We use &lt;code&gt;ffprobe&lt;/code&gt;, a companion tool to &lt;code&gt;ffmpeg&lt;/code&gt;, to inspect the video file and retrieve the codec name before constructing our &lt;code&gt;ffmpeg&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Here's the &lt;code&gt;ffprobe&lt;/code&gt; command we run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffprobe &lt;span class="nt"&gt;-v&lt;/span&gt; error &lt;span class="nt"&gt;-select_streams&lt;/span&gt; v:0 &lt;span class="nt"&gt;-show_entries&lt;/span&gt; &lt;span class="nv"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;codec_name &lt;span class="nt"&gt;-of&lt;/span&gt; &lt;span class="nv"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 /path/to/video.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-v error&lt;/code&gt;: Suppresses all logging except for errors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-select_streams v:0&lt;/code&gt;: Selects the first video stream.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-show_entries stream=codec_name&lt;/code&gt;: Instructs &lt;code&gt;ffprobe&lt;/code&gt; to only output the &lt;code&gt;codec_name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-of csv=p=0&lt;/code&gt;: Formats the output as plain text with no headers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This command reliably gives us the codec name, for example, &lt;code&gt;h264&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together in Code
&lt;/h2&gt;

&lt;p&gt;Here is the final implementation logic in our Python service.&lt;/p&gt;

&lt;p&gt;First, we get the &lt;code&gt;codec_name&lt;/code&gt; using &lt;code&gt;ffprobe&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ffprobe_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ffprobe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-v&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-select_streams&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-show_entries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream=codec_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-of&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;csv=p=0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;video_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_posix&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Error handling omitted for brevity...
&lt;/span&gt;
&lt;span class="n"&gt;ffprobe_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ffprobe_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;codec_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ffprobe_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we conditionally build our &lt;code&gt;ffmpeg&lt;/code&gt; arguments. If the video is H.264, we add our fix. Otherwise, we proceed as normal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ffmpeg_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="c1"&gt;# Conditionally add the metadata fix for H.264 videos
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;codec_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h24&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ffmpeg_args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-bsf:v&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h264_metadata=matrix_coefficients=1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Add the rest of the arguments for thumbnail extraction
&lt;/span&gt;&lt;span class="n"&gt;ffmpeg_args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-ss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe_timestamp_s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-noaccurate_seek&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-i&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;video_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_posix&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-frames:v&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_posix&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Execute the command
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ffmpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ffmpeg_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach ensures that we fix the color space issue for problematic H.264 files without breaking processing for any other video formats.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaway
&lt;/h2&gt;

&lt;p&gt;This experience was a powerful reminder that dependencies can and do change. A seemingly minor update in a tool like &lt;code&gt;ffmpeg&lt;/code&gt; can have significant ripple effects. By being explicit in our commands and building robust, conditional logic, we can create more resilient systems that are less susceptible to upstream changes. Happy coding! &lt;/p&gt;

</description>
      <category>ffmpeg</category>
    </item>
    <item>
      <title>The Pitfall of ORDER BY Timestamp (And How to Fix It)</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Tue, 30 Sep 2025 09:30:03 +0000</pubDate>
      <link>https://forem.com/andrewchaa/the-pitfall-of-order-by-timestamp-and-how-to-fix-it-3lh6</link>
      <guid>https://forem.com/andrewchaa/the-pitfall-of-order-by-timestamp-and-how-to-fix-it-3lh6</guid>
      <description>&lt;p&gt;As developers, we often need to fetch the "latest" record from a database. It could be the most recent status update, the last comment on a post, or the newest entry in a history table. The SQL seems obvious, right? You just &lt;code&gt;ORDER BY&lt;/code&gt; a timestamp column and grab the top one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Find the latest status for a model&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;model_status&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;model_status_history&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;model_uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'some-uid'&lt;/span&gt;
 &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
 &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query looks perfectly logical. But lurking beneath its simplicity is a subtle bug that can cause inconsistent results, especially in high-traffic applications. The "latest" record you get back might not actually be the one you expect.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: The Tie
&lt;/h3&gt;

&lt;p&gt;The issue arises when your system records multiple events within the same time resolution. For example, if your &lt;code&gt;created_at&lt;/code&gt; column stores time down to the millisecond, what happens when two records are inserted in the &lt;em&gt;exact same millisecond&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;When the database sees two rows with identical &lt;code&gt;created_at&lt;/code&gt; values, it considers them tied. Without a clear "tie-breaker" rule, the database makes no guarantee about which row will come first. In one query, it might return Row A. A moment later, under slightly different conditions, it might return Row B. This is called a non-deterministic sort, and it's a recipe for unpredictable behaviour.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Creating a Deterministic Sort
&lt;/h3&gt;

&lt;p&gt;To fix this, we need to give the database a tie-breaker—a second rule to apply when the first one isn't enough. The perfect candidate is a column that is guaranteed to be unique and sequential: the primary key &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Method 1: The Tie-Breaker
&lt;/h4&gt;

&lt;p&gt;Since a primary key is unique, it can break any tie. We can add it as a second condition to our &lt;code&gt;ORDER BY&lt;/code&gt; clause.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- First, sort by time. If there's a tie, sort by ID.&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the database:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; First, sort all records by &lt;code&gt;created_at&lt;/code&gt; in descending order.&lt;/li&gt;
&lt;li&gt; If any group of records has the exact same &lt;code&gt;created_at&lt;/code&gt; value, sort that specific group by &lt;code&gt;id&lt;/code&gt; in descending order.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because &lt;code&gt;id&lt;/code&gt; is unique, a tie is now impossible. The row with the highest &lt;code&gt;id&lt;/code&gt; (the one inserted last) will always be chosen. Your query is now deterministic—it will return the same, correct result every single time.&lt;/p&gt;

&lt;h4&gt;
  
  
  Method 2: The Simpler, Faster Way
&lt;/h4&gt;

&lt;p&gt;There's an even more direct approach. If your &lt;code&gt;id&lt;/code&gt; is an auto-incrementing integer, it already serves as a perfect timeline of events. A higher &lt;code&gt;id&lt;/code&gt; &lt;em&gt;always&lt;/em&gt; means the record was created later.&lt;/p&gt;

&lt;p&gt;This means you don't even need the timestamp for ordering. You can rely solely on the &lt;code&gt;id&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- The most reliable way to get the last inserted row&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is not only simpler but often more performant. Sorting on an indexed, integer primary key is typically faster for a database than sorting on a more complex &lt;code&gt;timestamp&lt;/code&gt; or &lt;code&gt;datetime&lt;/code&gt; column.&lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaway
&lt;/h3&gt;

&lt;p&gt;Relying on timestamps alone to find the "latest" record is a risk. Due to the possibility of ties, you can get inconsistent results.&lt;/p&gt;

&lt;p&gt;The best practice is simple: when you need the most recent entry, always sort by a unique, sequential column like your primary key. Whether you use it as a tie-breaker or as the sole ordering key, it guarantees your queries are robust, reliable, and predictable.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Securely Send SMS with Twilio, AWS Lambda, and Terraform</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Tue, 30 Sep 2025 07:23:39 +0000</pubDate>
      <link>https://forem.com/andrewchaa/securely-send-sms-with-twilio-aws-lambda-and-terraform-1h22</link>
      <guid>https://forem.com/andrewchaa/securely-send-sms-with-twilio-aws-lambda-and-terraform-1h22</guid>
      <description>&lt;p&gt;In modern applications, automated notifications are essential for user engagement. Sending an SMS is a direct and effective way to reach your customers. This guide will walk you through a secure, automated, and serverless solution for sending SMS messages using Twilio, running the code on AWS Lambda, and managing the infrastructure with Terraform.&lt;/p&gt;

&lt;p&gt;We'll stick to a key security principle: never hardcode your credentials. We'll see how to pass sensitive information like your Twilio &lt;code&gt;accountSid&lt;/code&gt; and &lt;code&gt;authToken&lt;/code&gt; securely from GitHub Secrets all the way to your Lambda function's environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AWS Lambda Function
&lt;/h3&gt;

&lt;p&gt;First, let's look at the Node.js/TypeScript code that will run on AWS Lambda. This function is responsible for the actual sending of the SMS.&lt;/p&gt;

&lt;p&gt;The magic happens in this simple script which uses the official &lt;code&gt;twilio&lt;/code&gt; SDK.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;twilio&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twilio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountSid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_ACCOUNT_SID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_FROM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Your Twilio phone number&lt;/span&gt;

&lt;span class="c1"&gt;// A crucial check to ensure the function has the credentials it needs&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;accountSid&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Twilio configuration is missing in environment variables.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;twilio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountSid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendTextMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Message sent successfully:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to send message:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Takeaways:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;No Hardcoded Secrets: Notice how &lt;code&gt;accountSid&lt;/code&gt;, &lt;code&gt;authToken&lt;/code&gt;, and the &lt;code&gt;from&lt;/code&gt; number are retrieved from &lt;code&gt;process.env&lt;/code&gt;. This is the standard way to access environment variables in Node.js. It keeps your sensitive data out of your source code.&lt;/li&gt;
&lt;li&gt;Error Handling: The code gracefully handles potential failures by wrapping the API call in a &lt;code&gt;try...catch&lt;/code&gt; block.&lt;/li&gt;
&lt;li&gt;Initialization: It performs a startup check to ensure the necessary environment variables are present. If not, the function will fail fast, which is better than failing silently later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Infrastructure: Defining the Lambda with Terraform
&lt;/h3&gt;

&lt;p&gt;Now, how do we get those environment variables into our Lambda function? We define them using Infrastructure as Code (IaC) with Terraform. This allows us to version control our cloud setup.&lt;/p&gt;

&lt;p&gt;The Terraform configuration below defines the Lambda function and, most importantly, injects our variables into its runtime environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"lambda_function_send_notification"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./lambda-function"&lt;/span&gt;

  &lt;span class="nx"&gt;function_name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_send_notification"&lt;/span&gt;
  &lt;span class="nx"&gt;role_arn&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iam_lambda_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;env_vars&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env_vars&lt;/span&gt; &lt;span class="c1"&gt;# This is where the magic happens!&lt;/span&gt;
  &lt;span class="c1"&gt;# ... other configurations&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;env_vars&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;TWILIO_ACCOUNT_SID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_ACCOUNT_SID&lt;/span&gt; &lt;span class="c1"&gt;# Mapping Terraform var to Lambda env var&lt;/span&gt;
    &lt;span class="nx"&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;  &lt;span class="c1"&gt;# Mapping Terraform var to Lambda env var&lt;/span&gt;
    &lt;span class="nx"&gt;TWILIO_FROM&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWILIO_FROM&lt;/span&gt;      &lt;span class="c1"&gt;# Add your Twilio 'from' number here&lt;/span&gt;
    &lt;span class="c1"&gt;# ... other variables&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Variable definitions to receive values from the CI/CD pipeline&lt;/span&gt;
&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"TWILIO_ACCOUNT_SID"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"TWILIO_AUTH_TOKEN"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"TWILIO_FROM"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Takeaways:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;env_vars&lt;/code&gt; Block: The &lt;code&gt;locals.env_vars&lt;/code&gt; map is the crucial link. It defines the key-value pairs that will become the environment variables inside the AWS Lambda function.&lt;/li&gt;
&lt;li&gt;Variable Mapping: We map the incoming Terraform variables (e.g., &lt;code&gt;var.TWILIO_ACCOUNT_SID&lt;/code&gt;) to the names our JavaScript code expects (e.g., &lt;code&gt;TWILIO_ACCOUNT_SID&lt;/code&gt;). This keeps the code clean and decouples it from the naming conventions used in your infrastructure or CI/CD system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Automation: CI/CD with GitHub Actions
&lt;/h3&gt;

&lt;p&gt;Finally, let's tie it all together. The GitHub Actions workflow automates the deployment. This is where we securely pull the actual secret values from &lt;strong&gt;GitHub Secrets&lt;/strong&gt; and pass them to our Terraform plan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# These TF_VAR_ prefixes tell Terraform to populate variables&lt;/span&gt;
      &lt;span class="na"&gt;TF_VAR_TWILIO_ACCOUNT_SID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.TWILIO_ACCOUNT_SID }}&lt;/span&gt;
      &lt;span class="na"&gt;TF_VAR_TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.TWILIO_AUTH_TOKEN }}&lt;/span&gt;
      &lt;span class="na"&gt;TF_VAR_TWILIO_FROM&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NAVIEN_TWILIO_FROM }}&lt;/span&gt; &lt;span class="c1"&gt;# Added the from number&lt;/span&gt;
      &lt;span class="c1"&gt;# ... other environment variables&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="c1"&gt;# ... build and test steps ...&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Terraform&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Init&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./terraforms&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform init&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Plan&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./terraforms&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform plan -input=false&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform Apply&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./terraforms&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform apply -auto-approve -input=false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Takeaways:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Secrets: The actual credentials (&lt;code&gt;${{ secrets.TWILIO_ACCOUNT_SID }}&lt;/code&gt;) are stored securely in your GitHub repository's settings. They are never printed in logs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TF_VAR_&lt;/code&gt; Prefix: The &lt;code&gt;TF_VAR_&lt;/code&gt; prefix is a special convention used by Terraform. When GitHub Actions sets an environment variable like &lt;code&gt;TF_VAR_TWILIO_ACCOUNT_SID&lt;/code&gt;, Terraform automatically uses its value for the &lt;code&gt;TWILIO_ACCOUNT_SID&lt;/code&gt; input variable in your &lt;code&gt;.tf&lt;/code&gt; files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Complete Secure Flow
&lt;/h3&gt;

&lt;p&gt;Here’s the full journey of your secret credentials, from storage to execution, without ever being exposed in your code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Storage: Your Twilio Account SID, Auth Token, and phone number are stored as GitHub Secrets.&lt;/li&gt;
&lt;li&gt;CI/CD Pipeline: The GitHub Actions workflow reads the secrets and exports them as environment variables prefixed with &lt;code&gt;TF_VAR_&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Infrastructure as Code: Terraform reads the &lt;code&gt;TF_VAR_&lt;/code&gt; variables and uses them to populate the &lt;code&gt;variable&lt;/code&gt; blocks in its configuration.&lt;/li&gt;
&lt;li&gt;Lambda Environment: Terraform then passes these values into the &lt;code&gt;environment variables&lt;/code&gt; section of the AWS Lambda function resource.&lt;/li&gt;
&lt;li&gt; Execution: When the Lambda function runs, the Node.js code accesses these variables via &lt;code&gt;process.env&lt;/code&gt; to securely authenticate with the Twilio API.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By following this pattern, you build a system that is not only automated and scalable but also secure by design. ✨&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>terraform</category>
      <category>security</category>
    </item>
    <item>
      <title>That Cryptic sbt Error? My JDK Was Too New</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Tue, 23 Sep 2025 16:27:50 +0000</pubDate>
      <link>https://forem.com/andrewchaa/that-cryptic-sbt-error-my-jdk-was-too-new-ap1</link>
      <guid>https://forem.com/andrewchaa/that-cryptic-sbt-error-my-jdk-was-too-new-ap1</guid>
      <description>&lt;p&gt;I was following Scala course on &lt;a href="https://www.coursera.org/" rel="noopener noreferrer"&gt;Coursera&lt;/a&gt;. I've downloaded the code for the given assignments, I've got the tests, and I was ready to go. I opened the terminal, navigate to the project directory, and type the magic words: &lt;code&gt;sbt test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, it happens. A wall of red text fills your screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error:
  bad constant pool index: 0 at pos: 48454
    while compiling: &amp;lt;no file&amp;gt;
...
Exception in thread "sbt-parser-init-thread" java.lang.ExceptionInInitializerError
Caused by: scala.reflect.internal.FatalError: 
  bad constant pool index: 0 at pos: 48454
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My first thought was, "What did I break?" I scanned the code, but I haven't even changed anything yet. This wasn't a simple typo or a missing semicolon. It was a deep, scary-looking error from the internals of the compiler.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Real Culprit: A Generational Gap
&lt;/h3&gt;

&lt;p&gt;This error was a tell-tale sign that my Java version (JDK) is too new for your &lt;code&gt;sbt&lt;/code&gt; version.&lt;/p&gt;

&lt;p&gt;When I hit this problem, the log showed I was running &lt;code&gt;sbt 1.8.2&lt;/code&gt; with &lt;code&gt;Java 21&lt;/code&gt;. Here’s what’s happening under the hood: The &lt;code&gt;sbt&lt;/code&gt; build tool is itself a Scala program, and older versions of it use an older Scala compiler (version 2.12). This older compiler simply doesn’t know how to handle the modern bytecode produced by a very new JDK like Java 21.&lt;/p&gt;

&lt;p&gt;Think of it like trying to play a vintage video game on a 4K TV. The TV is too advanced and doesn't understand the old signal format, so the screen just glitches out.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: SDKMAN! to the Rescue
&lt;/h3&gt;

&lt;p&gt;I could hunt down old Java versions and manually manage my &lt;code&gt;JAVA_HOME&lt;/code&gt; path for every project, but who has time for that? The best tool for this job was &lt;strong&gt;SDKMAN&lt;/strong&gt;, a command-line tool for managing parallel versions of multiple Software Development Kits.&lt;/p&gt;

&lt;p&gt;With SDKMAN!, you can install multiple Java versions and switch between them with a single command.&lt;/p&gt;

&lt;p&gt;Here’s how I solved the "bad constant pool index" error in under five minutes:&lt;/p&gt;

&lt;p&gt;Step 1: Install SDKMAN&lt;/p&gt;

&lt;p&gt;If you don't have it, just follow the simple instructions on the &lt;a href="https://sdkman.io/install" rel="noopener noreferrer"&gt;official SDKMAN website&lt;/a&gt;. It's usually a single &lt;code&gt;curl&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Step 2: Find and Install a Compatible JDK&lt;/p&gt;

&lt;p&gt;The sbt 1.x series works beautifully with JDK 11 or JDK 17. I opted for 17. You can list all available versions and then install your choice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List available Java versions&lt;/span&gt;
sdk list java

&lt;span class="c"&gt;# Install a compatible version (I chose Temurin)&lt;/span&gt;
sdk &lt;span class="nb"&gt;install &lt;/span&gt;java 17.0.12-tem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3: Switch Your Java Version&lt;/p&gt;

&lt;p&gt;This is the magic step. For your current terminal session, just tell SDKMAN which Java version to use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Switch to the newly installed JDK&lt;/span&gt;
sdk use java 17.0.12-tem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SDKMAN will reconfigure your shell's &lt;code&gt;PATH&lt;/code&gt; on the fly. You can verify the change with &lt;code&gt;java -version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Step 4: Run sbt Again&lt;/p&gt;

&lt;p&gt;Now, back in your project folder, run the command that started all this trouble.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sbt &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! The cryptic errors vanish. The build kicks off, dependencies download, tests run, and you see the beautiful green text of a successful test suite.&lt;/p&gt;

</description>
      <category>scala</category>
      <category>sbt</category>
      <category>java</category>
    </item>
    <item>
      <title>Running Your Expo App on a Real Device for Testing</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Sat, 06 Sep 2025 22:02:37 +0000</pubDate>
      <link>https://forem.com/andrewchaa/running-your-expo-app-on-a-real-device-for-testing-3ci</link>
      <guid>https://forem.com/andrewchaa/running-your-expo-app-on-a-real-device-for-testing-3ci</guid>
      <description>&lt;p&gt;As an Expo developer, you’ve likely spent countless hours running your app on a simulator or emulator. While that's great for most development, nothing beats the feel of your app on a real device. It's the only way to truly test performance, native features, and user experience.&lt;/p&gt;

&lt;p&gt;There are two primary paths for running your Expo app on a physical device: using the &lt;strong&gt;Expo Go&lt;/strong&gt; app or building a &lt;strong&gt;custom development client&lt;/strong&gt;. The right choice depends on your project's dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 1: The Fast Path with Expo Go
&lt;/h3&gt;

&lt;p&gt;If your project relies only on the standard APIs that come with the Expo SDK—meaning no custom native modules or libraries outside of what Expo Go supports—this is your go-to method. It’s quick and requires minimal setup.&lt;/p&gt;

&lt;h4&gt;
  
  
  Prerequisites
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Your Device: An iPhone or Android phone.&lt;/li&gt;
&lt;li&gt;Expo Go: Make sure the free &lt;strong&gt;Expo Go&lt;/strong&gt; app is installed on your device from the App Store or Google Play Store.&lt;/li&gt;
&lt;li&gt;Shared Network: This is the most critical step. Your computer and your mobile device must be connected to the exact same Wi-Fi network. Issues with corporate or university networks that have client isolation are the most common cause of connection failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step-by-Step Guide
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt; Start Your Development Server: In your project's root directory, open your terminal and run the command &lt;code&gt;npx expo start&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Scan the QR Code: The terminal will display a QR code. Use your device to scan it.

&lt;ul&gt;
&lt;li&gt;On iOS: Open the built-in Camera app and point it at the QR code. A notification will pop up. Tap it to open your app in Expo Go.&lt;/li&gt;
&lt;li&gt;On Android: Open the Expo Go app and tap the "Scan QR Code" button to use the in-app scanner.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; Run the App: Expo Go will connect to your computer, download the JavaScript bundle, and run your app. Now, whenever you save changes in your code, they'll instantly update on your device.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Accessing the Developer Menu
&lt;/h4&gt;

&lt;p&gt;Once the app is running, you can access the developer menu for debugging tools by shaking your device firmly. This menu allows you to open a remote debugger, reload the app, or view a performance monitor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 2: The Professional Path with a Development Build
&lt;/h3&gt;

&lt;p&gt;Once you add custom native modules—such as a specific video player, a payment library, or Firebase—the Expo Go app won't work anymore. Its core is immutable and doesn't contain the native code your new library needs. The solution is to create a custom development build, which is a personalized version of Expo Go with your project's unique native code compiled in.&lt;/p&gt;

&lt;h4&gt;
  
  
  Prerequisites
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Expo Account: You'll need a free Expo account.&lt;/li&gt;
&lt;li&gt;EAS CLI: Install the Expo Application Services (EAS) command-line interface by running &lt;code&gt;npm install -g eas-cli&lt;/code&gt; and then log in with &lt;code&gt;eas login&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step-by-Step Guide
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt; Add the Development Client Library: This library allows your custom-built app to communicate with your development server. Run &lt;code&gt;npx expo install expo-dev-client&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Configure the Build Profile: Make sure your &lt;code&gt;eas.json&lt;/code&gt; file is configured with a &lt;code&gt;development&lt;/code&gt; build profile that has &lt;code&gt;"developmentClient": true&lt;/code&gt;. If you don't have this file, you can generate it by running &lt;code&gt;eas build:configure&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Create the Development Build: This is the process of building the native &lt;code&gt;.apk&lt;/code&gt; (Android) or &lt;code&gt;.ipa&lt;/code&gt; (iOS) file. It's done on Expo's cloud servers and can take 10-20 minutes.

&lt;ul&gt;
&lt;li&gt;For Android: &lt;code&gt;eas build --profile development --platform android&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For iOS: &lt;code&gt;eas build --profile development --platform ios&lt;/code&gt; (Note: This requires a paid Apple Developer account.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; Install the Build: Once the build is complete, EAS will provide a link and a QR code. Scan the QR code to download and install the app on your device. On iOS, you'll likely use TestFlight for this.&lt;/li&gt;
&lt;li&gt; Run Your App:

&lt;ul&gt;
&lt;li&gt;Start your server with &lt;code&gt;npx expo start --dev-client&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Open the new custom app you just installed on your phone. It will automatically connect to your development server.&lt;/li&gt;
&lt;li&gt;Just like with Expo Go, you can shake your device to access the developer menu and debugging tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  When to Rebuild Your App
&lt;/h3&gt;

&lt;p&gt;With a development build, you don't need to rebuild every time you make a change. Your JavaScript code is loaded dynamically from the development server, providing a fast and iterative experience.&lt;/p&gt;

&lt;p&gt;You must create a new build only when you change the native code. This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding or removing a native module npm package.&lt;/li&gt;
&lt;li&gt;Upgrading or downgrading an existing native module.&lt;/li&gt;
&lt;li&gt;Changing native configuration files like &lt;code&gt;app.json&lt;/code&gt;, &lt;code&gt;Info.plist&lt;/code&gt;, or &lt;code&gt;AndroidManifest.xml&lt;/code&gt; (e.g., updating your app icon or package name).&lt;/li&gt;
&lt;li&gt;Upgrading your project to a new Expo SDK version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For everything else—like editing your JavaScript/TypeScript files or changing CSS styles—the Metro server will instantly push the updates to your running app.&lt;/p&gt;

&lt;p&gt;This two-path approach makes Expo incredibly flexible, allowing you to choose the right workflow for your project at every stage of development.&lt;/p&gt;

</description>
      <category>reactnative</category>
    </item>
    <item>
      <title>Fix for the Android Black Screen Bug on Camera Re-entry</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Thu, 04 Sep 2025 12:19:14 +0000</pubDate>
      <link>https://forem.com/andrewchaa/fix-for-the-android-black-screen-bug-on-camera-re-entry-2opc</link>
      <guid>https://forem.com/andrewchaa/fix-for-the-android-black-screen-bug-on-camera-re-entry-2opc</guid>
      <description>&lt;p&gt;Have you ever opened a barcode scanner in an app, clicked the back button, and returned to find a black screen where the camera view should be? This is a frustratingly common bug on Android devices, and it's a symptom of a deeper issue with how mobile apps handle camera resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the Root Cause: The "Stale Camera" Problem
&lt;/h3&gt;

&lt;p&gt;The black screen bug on Android is essentially a &lt;strong&gt;timing issue&lt;/strong&gt;. Here's a breakdown of what's happening behind the scenes:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Android's Aggressive Resource Management
&lt;/h4&gt;

&lt;p&gt;Unlike iOS, Android is designed to aggressively manage resources to optimize memory. When a user navigates away from the camera screen (e.g., by pressing the "back" button), the Android operating system may suspend or even release the camera hardware session to free up resources.&lt;/p&gt;

&lt;p&gt;When the user returns to the app, the camera component in memory is "stale." It still exists, but the underlying native camera session has been terminated. This leaves a black, empty view.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. The Race Condition
&lt;/h4&gt;

&lt;p&gt;The core of the problem lies in a &lt;strong&gt;race condition&lt;/strong&gt; between the app's component lifecycle and the native camera's hardware initialization.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;User Navigates Back:&lt;/strong&gt; The screen comes back into focus.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;React Component Renders:&lt;/strong&gt; The React Native &lt;code&gt;CameraView&lt;/code&gt; component re-renders almost instantly.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Black Screen Appears:&lt;/strong&gt; The native camera hardware is still in the process of starting up.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Premature Scanning:&lt;/strong&gt; In a flawed implementation, the app might try to enable barcode scanning even though the camera isn't ready, leading to crashes or no results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is a frustrating black screen for the user until the camera finally reinitializes, if it ever does.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: State-Driven Camera Management
&lt;/h3&gt;

&lt;p&gt;My solution tackles this issue head-on by creating a system that ensures the app never tries to scan until the camera is 100% ready. Here's a look at the key changes we implemented:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. &lt;strong&gt;Camera State Management&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;I introduced a new state variable, &lt;code&gt;isCameraReady&lt;/code&gt;, to track the camera's status. Barcode scanning is only enabled when this variable is &lt;code&gt;true&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CameraView&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cameraKey&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;absoluteFillObject&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// onBarcodeScanned is only active when isCameraReady is true&lt;/span&gt;
  &lt;span class="na"&gt;onBarcodeScanned&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isCameraReady&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;handleBarCodeScanned&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onCameraReady&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleCameraReady&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;barcodeScannerSettings&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;barcodeTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code128&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code39&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ean13&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ean8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upc_a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upc_e&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;autofocus&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'on'&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScanningOverlay&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;CameraView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By assigning &lt;code&gt;undefined&lt;/code&gt; to &lt;code&gt;onBarcodeScanned&lt;/code&gt; when the camera isn't ready, we completely disable the native barcode detection process. This is more efficient than constantly scanning and checking the state inside the callback function. It saves battery and CPU resources.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. &lt;strong&gt;Enhanced Focus Effect and Forced Remount&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We use &lt;code&gt;useFocusEffect&lt;/code&gt; to handle navigation events. When the screen comes into focus, we first reset the camera state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useFocusEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Reset camera state when screen comes into focus&lt;/span&gt;
    &lt;span class="nf"&gt;setIsCameraReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;setCameraKey&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prevKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prevKey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Force camera remount&lt;/span&gt;
    &lt;span class="nf"&gt;setIsScanning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Reset scanning state&lt;/span&gt;

    &lt;span class="c1"&gt;// Small delay to ensure camera is properly reinitialized on Android&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setIsCameraReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setIsCameraReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Incrementing the &lt;code&gt;cameraKey&lt;/code&gt; forces the &lt;code&gt;CameraView&lt;/code&gt; component to completely remount. This ensures it's not a "stale" component but a fresh instance ready for initialization.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. &lt;strong&gt;Explicit Camera Ready Handler&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;The critical part of the fix is waiting for the native camera to tell us it's ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Handle camera ready state (important for Android)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleCameraReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setIsCameraReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CameraView&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cameraKey&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onCameraReady&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleCameraReady&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// This callback is key&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;onCameraReady&lt;/code&gt; callback is a native-level event that fires only after the camera hardware has fully initialized and the preview stream has started. When this callback fires, we can confidently set &lt;code&gt;isCameraReady&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, and the app can begin scanning.&lt;/p&gt;

&lt;p&gt;We also added a small delay as a fallback, which helps in a small number of edge cases where the &lt;code&gt;onCameraReady&lt;/code&gt; event might be delayed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary of Benefits
&lt;/h3&gt;

&lt;p&gt;This comprehensive solution completely resolves the Android black screen issue by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixing Black Screens:&lt;/strong&gt; By forcing a proper camera re-initialization on navigation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preventing Premature Scanning:&lt;/strong&gt; Barcode detection is only enabled when the camera is confirmed ready.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improving Performance:&lt;/strong&gt; The app avoids unnecessary CPU usage by disabling scanning when the camera isn't active.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preventing Memory Leaks:&lt;/strong&gt; We've included a cleanup function that resets state and clears timers when the user leaves the screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The black screen bug is a classic example of the challenges of working with native hardware in a cross-platform environment. Our fix provides a robust and reliable solution, ensuring a smooth and frustration-free experience for users on both Android and iOS.&lt;/p&gt;

</description>
      <category>reactnative</category>
    </item>
    <item>
      <title>Know your videos: Inspecting &amp; Transcoding with FFmpeg and Homebrew</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Tue, 12 Aug 2025 11:52:12 +0000</pubDate>
      <link>https://forem.com/andrewchaa/know-your-videos-inspecting-transcoding-with-ffmpeg-and-homebrew-5c5i</link>
      <guid>https://forem.com/andrewchaa/know-your-videos-inspecting-transcoding-with-ffmpeg-and-homebrew-5c5i</guid>
      <description>&lt;p&gt;Have you ever wondered what's inside a video file? Or maybe you need to convert a video to a different format for better compatibility or smaller file size. The command line tools &lt;strong&gt;FFmpeg&lt;/strong&gt; and &lt;strong&gt;ffprobe&lt;/strong&gt; are your secret weapons for these tasks. We'll walk you through how to install them and then use them to inspect and transcode a video.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing FFmpeg with Homebrew
&lt;/h3&gt;

&lt;p&gt;If you're on a macOS, the easiest way to install FFmpeg is with &lt;strong&gt;Homebrew&lt;/strong&gt;, the "missing package manager for macOS."&lt;/p&gt;

&lt;p&gt;If you don't have Homebrew installed, you'll need to do that first by following the instructions on their official website. Once Homebrew is ready, installing FFmpeg is a breeze. Open your Terminal and type this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;ffmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the installation is complete, it's a good idea to confirm that everything is working. You can do this by checking the version of FFmpeg that was installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see the version information displayed, you're all set!&lt;/p&gt;




&lt;h3&gt;
  
  
  Checking a Video's Codec with ffprobe
&lt;/h3&gt;

&lt;p&gt;Before you change a video, it's smart to know what you're starting with. That's where &lt;strong&gt;ffprobe&lt;/strong&gt; comes in. It's a powerful command-line tool that's part of the FFmpeg suite. It can gather a ton of information about your multimedia files, including the video and audio codecs.&lt;/p&gt;

&lt;p&gt;To find out the codec of a video file named &lt;code&gt;input.mp4&lt;/code&gt;, simply run this command in your Terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffprobe ./input.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will print out a detailed report about the video, including its codec, resolution, framerate, and more. Look for the "Stream #0:0(eng): Video:" section to find the codec information.&lt;/p&gt;




&lt;h3&gt;
  
  
  Transcoding to H.264 with ffmpeg
&lt;/h3&gt;

&lt;p&gt;Now for the fun part: changing the video! &lt;strong&gt;Transcoding&lt;/strong&gt; is the process of converting a video file from one codec or format to another. The &lt;strong&gt;H.264&lt;/strong&gt; codec is a widely used and highly compatible standard, making it a great choice for web videos, mobile devices, and more.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ffmpeg&lt;/code&gt; command has many options, and it can seem a bit intimidating at first. Let's break down a common command used to transcode a video to H.264:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-preset&lt;/span&gt; medium &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's dissect this command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-i input.mp4&lt;/code&gt;: This is the &lt;strong&gt;input file&lt;/strong&gt;. It tells &lt;code&gt;ffmpeg&lt;/code&gt; which video to process.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-c:v libx264&lt;/code&gt;: This is the &lt;strong&gt;video codec&lt;/strong&gt;. We're telling &lt;code&gt;ffmpeg&lt;/code&gt; to use the &lt;code&gt;libx264&lt;/code&gt; encoder, which is the H.264 implementation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-crf 23&lt;/code&gt;: This stands for &lt;strong&gt;Constant Rate Factor&lt;/strong&gt;. It's a quality setting for the video. A lower number means higher quality and a larger file size, while a higher number means lower quality and a smaller file size. A value of &lt;code&gt;23&lt;/code&gt; is a good starting point for a balanced output.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-preset medium&lt;/code&gt;: This option controls the &lt;strong&gt;encoding speed&lt;/strong&gt;. Faster presets (like &lt;code&gt;ultrafast&lt;/code&gt;) result in lower quality and larger file sizes, while slower presets (like &lt;code&gt;veryslow&lt;/code&gt;) result in higher quality and smaller file sizes. &lt;code&gt;medium&lt;/code&gt; is a great balance.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-c:a aac&lt;/code&gt;: This is the &lt;strong&gt;audio codec&lt;/strong&gt;. We're specifying the AAC (Advanced Audio Coding) codec, which is a common and efficient choice.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-b:a 128k&lt;/code&gt;: This sets the &lt;strong&gt;audio bitrate&lt;/strong&gt; to &lt;code&gt;128k&lt;/code&gt;, which is a good quality for most purposes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;output.mp4&lt;/code&gt;: This is the &lt;strong&gt;output file name&lt;/strong&gt;. &lt;code&gt;ffmpeg&lt;/code&gt; will save the transcoded video to this file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After running this command, you'll have a new video file named &lt;code&gt;output.mp4&lt;/code&gt; that's ready for its new purpose!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Classic TDD: Less Mocking, More Confidence</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Wed, 16 Apr 2025 17:33:01 +0000</pubDate>
      <link>https://forem.com/andrewchaa/classic-tdd-less-mocking-more-confidence-483h</link>
      <guid>https://forem.com/andrewchaa/classic-tdd-less-mocking-more-confidence-483h</guid>
      <description>&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%2Fqynasy6gkomhtgf7tkpd.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%2Fqynasy6gkomhtgf7tkpd.png" alt="Image description" width="284" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Test-Driven Development (TDD) is a powerful practice, but it's easy to get bogged down in excessive mocking, leading to brittle tests and a false sense of security. This post explores the principles of "Classic TDD" and how focusing on testing behavior over implementation details can lead to more robust and maintainable code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem with Over-Mocking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many developers, even experienced ones, find testing a chore. Why? Often, it's because we end up writing more test code than implementation code. A single change in the implementation can break a large number of tests, not because the behavior changed, but because the tests were tightly coupled to internal details. This leads to frustration and a reluctance to refactor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Classic TDD Approach&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Classic TDD, as explained by Kent Beck in "TDD By Example", offers a different approach. The core idea is that internal code changes should not break existing tests. To achieve this, we need to shift our focus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test Behavior, Not Implementation:&lt;/strong&gt; Instead of focusing on testing individual methods or classes, we should test the public interface of our system. This could be an API endpoint, a queue message, or any other point of interaction with the system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Trigger for Tests:&lt;/strong&gt; Don't write a test just because you added a new method. The trigger for writing a test should be the need to implement a requirement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mock Sparingly:&lt;/strong&gt; Mocks should be used as little as possible.  Ideally, only mock 3rd-party libraries or external dependencies. Avoid mocking your own implementation code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benefits of Classic TDD&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By following these principles, we can reap significant benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Increased Test Coverage:&lt;/strong&gt; Testing behavior naturally leads to better coverage of the system's functionality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Realistic Tests:&lt;/strong&gt; Tests become more realistic and reflect how the system is actually used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier Refactoring:&lt;/strong&gt; Because tests are focused on behavior, you can refactor your code with confidence, knowing that you won't break tests unless you change the external behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less Brittle Tests:&lt;/strong&gt; Fewer mocks mean less brittle tests that are less likely to break with implementation changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less Code:&lt;/strong&gt; By avoiding excessive mocking, you write less test code overall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More Confidence:&lt;/strong&gt; Ultimately, Classic TDD leads to greater confidence in your code and your ability to maintain and evolve it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The System Under Test (SUT)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's crucial to remember that the System Under Test (SUT) is not a single class, but the public interface of a system. Tests should be written against this stable contract, focusing on the output of the implementation code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Classic TDD, with its emphasis on testing behavior and minimizing mocks, offers a path to more robust, maintainable, and reliable software. By embracing these principles, we can move away from brittle tests and towards a development process that fosters confidence and enables continuous improvement.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>Set up a new Mac</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Mon, 14 Apr 2025 17:36:49 +0000</pubDate>
      <link>https://forem.com/andrewchaa/set-up-a-new-mac-a9h</link>
      <guid>https://forem.com/andrewchaa/set-up-a-new-mac-a9h</guid>
      <description>&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%2Fwdrnd6e1ablu8dj3whtf.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%2Fwdrnd6e1ablu8dj3whtf.png" alt=" " width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I bought a new MacBook, as I thought the new US tariff would increase the price of Apple products. This is how I set up a new Mac. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;a href="https://ohmyz.sh/#install" rel="noopener noreferrer"&gt;oh my zsh&lt;/a&gt;: &lt;code&gt;sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://brew.sh" rel="noopener noreferrer"&gt;brew&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, I install my favourite apps&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome: &lt;code&gt;brew install --cask google-chrome&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Rectangle: &lt;code&gt;brew install --cask rectangle&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Warp: &lt;code&gt;brew install --cask warp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Whats App Desktop: &lt;code&gt;brew install --cask whatsapp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Obsidian: &lt;code&gt;brew install --cask obsidian&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Slack: &lt;code&gt;brew install --cask slack&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apps.apple.com/gb/app/perplexity-ask-anything/id6714467650?mt=12" rel="noopener noreferrer"&gt;Perplexity AI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adjust the settings&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete all the apps from the Dock: &lt;code&gt;defaults delete com.apple.dock persistent-apps &amp;amp;&amp;amp; killall Dock&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Turn on Auto hiding on Dock.&lt;/li&gt;
&lt;li&gt;Enable 3 finger drag and drop.&lt;/li&gt;
&lt;li&gt;Add a new language of your choice. Mine is Korean. Go to Settings &amp;gt; Keyboard &amp;gt; Text Input &amp;gt; Edit.&lt;/li&gt;
&lt;li&gt;Use F1, F2 as function key: Settings &amp;gt; Keyboard &amp;gt; Keyboard Shortcuts &amp;gt; Function Keys&lt;/li&gt;
&lt;li&gt;Enable Keyboard navigation: Settings &amp;gt; Keyboard &amp;gt; Keyboard navigation&lt;/li&gt;
&lt;li&gt;Display application switcher on every screen in multi-monitor setup: &lt;code&gt;defaults write com.apple.dock appswitcher-all-displays -bool true&lt;/code&gt; Then &lt;code&gt;killall Dock&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install developer tools&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/download/all/?q=Xcode" rel="noopener noreferrer"&gt;XCode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Github CLI: &lt;code&gt;brew install gh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;VS Code: &lt;code&gt;brew install --cask visual-studio-code&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MongoDB Compass: &lt;code&gt;brew install --cask mongodb-compass&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Postman: &lt;code&gt;brew install --cask postman&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Minikube: &lt;code&gt;brew install minikube&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Terraform: &lt;code&gt;brew install terraform&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fork: &lt;code&gt;brew install --cask fork&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set up Git&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ssh-keygen -t ed25519 -C "your_email@example.com"&lt;/code&gt;: to generate a new SSH key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eval "$(ssh-agent -s)"&lt;/code&gt;: to start the ssh agent&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;~/.ssh/config&lt;/code&gt; to automatically load keys into the ssh-agent
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Host github.com
  AddKeysToAgent &lt;span class="nb"&gt;yes
  &lt;/span&gt;UseKeychain &lt;span class="nb"&gt;yes
  &lt;/span&gt;IdentityFile ~/.ssh/id_ed25519
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ssh-add --apple-use-keychain ~/.ssh/id_ed25519&lt;/code&gt;: add the SSH private key to the ssh-agent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pbcopy &amp;lt; ~/.ssh/id_ed25519.pub&lt;/code&gt;: copy the public key&lt;/li&gt;
&lt;li&gt;Go to &lt;a href="https://github.com/settings/keys" rel="noopener noreferrer"&gt;Github settings&lt;/a&gt; and add the key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git config --global user.name "Your Name"&lt;/code&gt;: set your username&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git config --global user.email "your.email@example.com"&lt;/code&gt;: set your email&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git config pull.rebase false&lt;/code&gt;: set the pull action to be merge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set up VS Code&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shell Command: Install 'code' command in PATH&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set up Node.js&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;curl https://get.volta.sh | bash&lt;/code&gt;: to install &lt;a href="https://docs.volta.sh/guide/getting-started" rel="noopener noreferrer"&gt;Volta&lt;/a&gt;: &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;volta install node&lt;/code&gt;: to install the latest LTS node &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm i -g eas-cli&lt;/code&gt;: to install eas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set up Ruby &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;brew install ruby@3.2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;export PATH="/opt/homebrew/opt/ruby@3.2/bin:$PATH"&lt;/code&gt; to &lt;code&gt;~/.zshrc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;source ~/.zshrc&lt;/code&gt;: to reload the settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set up XCode&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if XCode is in the correct path: &lt;code&gt;xcode-select -p&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If it's in a different place like this, &lt;code&gt;/Library/Developer/CommandLineTools&lt;/code&gt;, reset the path: &lt;code&gt;sudo xcode-select -s /Applications/Xcode.app/Contents/Developer&lt;/code&gt;. This is necessary to build a React Native project successfully locally &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set up Python&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;brew install pyenv&lt;/code&gt;: to install python version manager&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;~/.zshrc&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PYENV_ROOT="$HOME/.pyenv"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'[[ -d $PYENV_ROOT/bin ]] &amp;amp;&amp;amp; export PATH="$PYENV_ROOT/bin:$PATH"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'eval "$(pyenv init - zsh)"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc

&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pyenv install 3.12&lt;/code&gt;: to install python 3.12&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pyenv global 3.12&lt;/code&gt;: to set the global version&lt;/li&gt;
&lt;li&gt;Install Python extension from Microsoft in VS Code&lt;/li&gt;
&lt;li&gt;Install Jupyter extension in VS Code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Set up AWS&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aws configure&lt;/code&gt; to set &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;aws_access_key_id&lt;/code&gt;, and &lt;code&gt;aws_secret_access_key&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Python `try` Blocks: A Breath of Fresh Air for Variable Scope (Unlike Java &amp; C#!)</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Mon, 10 Mar 2025 13:57:06 +0000</pubDate>
      <link>https://forem.com/andrewchaa/python-try-blocks-a-breath-of-fresh-air-for-variable-scope-unlike-java-c-1k6l</link>
      <guid>https://forem.com/andrewchaa/python-try-blocks-a-breath-of-fresh-air-for-variable-scope-unlike-java-c-1k6l</guid>
      <description>&lt;p&gt;Python's &lt;code&gt;try...except&lt;/code&gt; error handling is fantastic for writing robust code. But beyond just catching errors, Python offers a subtle yet incredibly convenient feature related to variable scope within &lt;code&gt;try&lt;/code&gt; blocks.  If you're coming from languages like Java or C#, you might be pleasantly surprised – and relieved!&lt;/p&gt;

&lt;p&gt;Have you ever defined a variable inside a &lt;code&gt;try&lt;/code&gt; block, hoping to use it later, only to find it's inaccessible outside in other languages?  &lt;strong&gt;Python elegantly sidesteps this frustration.&lt;/strong&gt;  In Python, variables defined within a &lt;code&gt;try&lt;/code&gt; block are readily available &lt;em&gt;outside&lt;/em&gt; of it, as long as the &lt;code&gt;try&lt;/code&gt; block completes successfully without raising an exception. This is a real time-saver and makes your code cleaner and more intuitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python's Convenient Scoping: No Need for Extra Boilerplate!
&lt;/h3&gt;

&lt;p&gt;Python's variable scoping rules are inherently flexible. Variables declared in blocks like &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;, and yes, &lt;code&gt;try&lt;/code&gt; and &lt;code&gt;except&lt;/code&gt;, are generally accessible in the surrounding scope.  This "forgiving" approach means you don't have to jump through hoops to use variables created within a &lt;code&gt;try&lt;/code&gt; block – they are simply there for you if the &lt;code&gt;try&lt;/code&gt; block runs smoothly.&lt;/p&gt;

&lt;p&gt;This is in stark contrast to languages like Java and C#. In those languages, variables declared &lt;em&gt;inside&lt;/em&gt; a &lt;code&gt;try&lt;/code&gt; block are typically scoped &lt;em&gt;only&lt;/em&gt; to that block.  If you want to use them outside, you often need to declare the variable &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;try&lt;/code&gt; block and then potentially assign it within, just to make it accessible later.  This adds extra lines of code and can feel unnecessarily verbose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Python's Simplicity in Action
&lt;/h3&gt;

&lt;p&gt;Let's see how Python's approach shines in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Variable defined inside try
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;An error occurred: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;#  `value` is immediately accessible here! How convenient!
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: Hello, World!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See how clean and direct that is?  We define &lt;code&gt;value&lt;/code&gt; inside the &lt;code&gt;try&lt;/code&gt; block, and if no error occurs, we can use &lt;code&gt;value&lt;/code&gt; directly afterwards without any extra steps.  Python just "gets it" and makes it easy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagine the Java/C# equivalent (simplified):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```java // Or similar C#&lt;br&gt;
String value; // Need to declare it &lt;em&gt;outside&lt;/em&gt;&lt;br&gt;
try {&lt;br&gt;
    value = "Hello, World!"; // Assign inside try&lt;br&gt;
} catch (Exception e) {&lt;br&gt;
    System.out.println("An error occurred: " + e);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Now &lt;code&gt;value&lt;/code&gt; is accessible because we declared it outside!&lt;br&gt;
System.out.println(value);&lt;/p&gt;

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


Notice the extra line of code in Java/C# to declare `value` *before* the `try` block?  Python eliminates this extra step, making your code more concise and readable.

### What About Exceptions?  Python Still Handles It Gracefully

Of course, error handling is still crucial.  If an exception *does* occur within the `try` block, Python jumps to the `except` block as expected.  And just like before, if an exception is raised *before* a variable is defined within the `try` block, that variable won't be available later.

However, even in error scenarios, Python's approach can be more convenient.  You can still initialize variables *before* the `try` block if you need them to have a default value in case of errors, but you're not *forced* to do so in every successful case just to make the variable accessible outside.

**Example highlighting error handling and convenience:**



```python
result = None # Initialize outside if needed for error cases, but not always necessary!
try:
    # Potentially error-prone operation
    result = calculate_important_value()
except ValueError as ve:
    print(f"ValueError during calculation: {ve}")
    result = "Default Value" # Or handle error differently

# `result` is always accessible here - either calculated value or default
print(f"The result is: {result}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we initialize &lt;code&gt;result&lt;/code&gt; to &lt;code&gt;None&lt;/code&gt; as a safety net. But even if &lt;code&gt;calculate_important_value()&lt;/code&gt; succeeds, we didn't &lt;em&gt;need&lt;/em&gt; to declare &lt;code&gt;result&lt;/code&gt; outside the &lt;code&gt;try&lt;/code&gt; block just to use it later.  Python's flexibility gives you options without adding unnecessary boilerplate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary: Python's &lt;code&gt;try&lt;/code&gt; Block Scoping is Just… Easier!
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python is Convenient:&lt;/strong&gt; Variables defined within a &lt;code&gt;try&lt;/code&gt; block are readily accessible outside if the &lt;code&gt;try&lt;/code&gt; block completes successfully. This saves you from extra steps and makes your code cleaner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contrast with Java/C#:&lt;/strong&gt;  Languages like Java and C# often require you to declare variables outside the &lt;code&gt;try&lt;/code&gt; block to use them later, adding extra lines of code. Python avoids this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Still Handles Errors Well:&lt;/strong&gt; Python's &lt;code&gt;try...except&lt;/code&gt; blocks still provide robust error handling. You can initialize variables outside if you need default values in error scenarios, but you're not forced to for successful paths.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Python's approach to variable scope in &lt;code&gt;try&lt;/code&gt; blocks is a testament to its design philosophy: making things easier and more intuitive for the developer. It's a small detail, but it contributes significantly to the overall pleasantness and efficiency of writing Python code, especially when compared to more restrictive languages.  Embrace this convenience and enjoy writing cleaner, more Pythonic code!&lt;/p&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>Solving the Jest Native Module Error in a React Native Expo Project</title>
      <dc:creator>Youngho Andrew Chaa</dc:creator>
      <pubDate>Sun, 28 Jul 2024 11:28:57 +0000</pubDate>
      <link>https://forem.com/andrewchaa/solving-the-jest-native-module-error-in-a-react-native-expo-project-8ea</link>
      <guid>https://forem.com/andrewchaa/solving-the-jest-native-module-error-in-a-react-native-expo-project-8ea</guid>
      <description>&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%2Fbz3bauhnk7n38s5k6poz.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%2Fbz3bauhnk7n38s5k6poz.png" alt="Image description" width="600" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While working on a React Native Expo project, I encountered a perplexing error message during my Jest tests. The error halted my progress and left me searching for a solution. Here's a detailed account of the error and how I resolved it &lt;/p&gt;

&lt;p&gt;The error message I encountered was as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot find native module 'ExpoClipboard'

   7 |   Box,
   8 | } from 'native-base'
&amp;gt;  9 | import * as Clipboard from 'expo-clipboard'
     | ^
  10 |
  11 | import { Job, JobStatus, Service, Photo } from '../../types/JobModel'
  12 | import { NativeStackNavigationProp } from '@react-navigation/native-stack'

  at Object.requireNativeModule (node_modules/expo-modules-core/src/requireNativeModule.ts:17:11)
  at requireNativeModule (node_modules/jest-expo/src/preset/setup.js:257:73)
  at Object.&amp;lt;anonymous&amp;gt; (node_modules/expo-clipboard/src/ExpoClipboard.ts:3:35)
  at Object.&amp;lt;anonymous&amp;gt; (node_modules/expo-clipboard/src/Clipboard.ts:11:1)
  at Object.&amp;lt;anonymous&amp;gt; (src/screens/JobEdit/index.tsx:9:1)
  at Object.&amp;lt;anonymous&amp;gt; (src/screens/JobEdit/index.test.tsx:4:1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error message clearly indicated that Jest was unable to find the native module &lt;code&gt;ExpoClipboard&lt;/code&gt;. This issue arises because Jest tests run in a Node environment, which does not support native modules used in a React Native application.&lt;/p&gt;

&lt;p&gt;After some research, I found a solution to mock the &lt;code&gt;expo-clipboard&lt;/code&gt; module in the Jest environment. By mocking the module, I could bypass the need for the native environment and ensure my tests ran smoothly. Here’s the code that solved the issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-clipboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;getStringAsync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet creates a mock version of the &lt;code&gt;expo-clipboard&lt;/code&gt; module, providing mock functions for &lt;code&gt;getStringAsync&lt;/code&gt; and &lt;code&gt;setString&lt;/code&gt;. This mock setup ensures that any calls to these functions within the tests will use the mocked versions instead of attempting to access the real native module.&lt;/p&gt;

&lt;p&gt;To apply this solution, you need to add the mocking code to your Jest setup file. If you don't have a Jest setup file, you can create one, typically named &lt;code&gt;jestSetupFile.js&lt;/code&gt;. Here's how to include the mock:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create or open your Jest setup file (e.g., &lt;code&gt;jestSetupFile.js&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Add the mock code to the file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-clipboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="na"&gt;getStringAsync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="na"&gt;setString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
   &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Ensure that your Jest configuration is set to use this setup file. You can do this in your &lt;code&gt;jest.config.js&lt;/code&gt; or &lt;code&gt;package.json&lt;/code&gt; under the Jest configuration section:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nl"&gt;"jest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"setupFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;rootDir&amp;gt;/jestSetupFile.js"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the mock in place, you can now run your tests again. The error should be resolved, and Jest will use the mocked functions instead of trying to access the native &lt;code&gt;ExpoClipboard&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;Encountering issues like the native module error in Jest can be frustratin. By mocking the &lt;code&gt;expo-clipboard&lt;/code&gt; module, I was able to run my tests without errors, allowing me to continue developing my React Native Expo project smoothly.&lt;/p&gt;

&lt;p&gt;If you encounter similar issues with other native modules, consider mocking them in the Jest environment using the same approach. Happy testing!&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>jest</category>
    </item>
  </channel>
</rss>
