• Stack Overflow Keycap Reward

    My tiny reward from Stackoverflow arrived yesterday, a small single keycap. I won it when I answered their Saved the Day story. I did not remember when, it was a long time ago.

    Initially, I told them to not even bother shipping a small item overseas, the Philippines' Customs are not worth the trouble.

    But they insisted. Thanks!

  • New MacBook Air M2

    My new Macbook came in today, from my sister in Houston. Thank you!

    Coming from Macbook Pro 2015 (won’t run the latest Ventura or Xcode 14.3 anymore), this is a huge leap for me.

    I just want to take note of the software and applications I initially installed for future reference.

    Applications Installed in order

  • Weekend Project: ImageDownloader.py

    My personal project for this weekend is to migrate my portfolio from my old WordPress blog to my current one.

    Export

    First, I exported my content using WordPress admin. And was saved into a lwgmnzme.wordpress.com-2022-09-25-02_03_10 folder in XML format.

    WordPress XML to Markdown

    I then used this handy library for migrating all the pages to Markdown.

    Curl Option

    At first I was downloading each image individually, and it seems I have more than 50 images to process. So this is not an option.

    1
    
    curl https://lwgmnzme.files.wordpress.com/2021/02/simulator-screen-shot-iphone-12-pro-2021-02-27-at-17.22.11.png > image1.png
    

    Python Script

    Time to brush up my Python skills once again. I downloaded the requests Python library.

    1
    
    sudo pip3 install requests
    

    The plan is to be able to input several URLs and maybe prompt a shortcut key or something to cue the script that it’s time to download the images.

    Or better yet look for the png URLs inside the .md file? Seems like the most logical option for me.

    Initial commit

    1
    2
    3
    4
    5
    
    import requests
    
    file = open("index.md", "r")
    line = file.read()
    print("Read = %s" % (line))
    

    So far, so good.

    Find all the png files

    The next step is to find all the URL images with .png extensions. Time to use Regex I guess.

    Update the scripts.

    1
    2
    3
    4
    5
    6
    7
    8
    
    import requests
    import re
    
    file = open("index.md", "r")
    line = file.read()
    
    result = re.findall(r'(https?://[^\s]+)', line)
    print(result)
    

    Looks promising. Let’s loop through it.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    import requests
    import re
    
    file = open("index.md", "r")
    line = file.read()
    
    results = re.findall(r'(https?://[^\s]+)', line)
    
    for result in results:
    	print(result)
    

    Much clearer. Okay, let’s try downloading each one of them. But, wait I noticed an extra ) character on the results. The hell.

    Maybe I should remove the extra ?w= while I’m at it too.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    import requests
    import re
    
    file = open("index.md", "r")
    line = file.read()
    
    results = re.findall(r'(https?://[^\s]+)', line)
    
    for result in results:
    	indexOfPng = result.find("?")
    	updatedResult = result[:indexOfPng]
    	print(updatedResult)
    

    Not the most elegant of solutions but it worked.

    Get the filename

    The plan is to get the filename from the URL and use it for saving as a file.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    import requests
    import re
    import os
    from urllib.parse import urlparse
    
    file = open("index.md", "r")
    line = file.read()
    
    results = re.findall(r'(https?://[^\s]+)', line)
    
    for result in results:
    	indexOfPng = result.find("?")
    	updatedResult = result[:indexOfPng]
    	# Get the filename
    	parse = urlparse(result)
    	print(os.path.basename(parse.path))
    

    Time to download

    I was having a IsADirectoryError trouble. And it seems that I need to filter out to download only files coming from a wordpress.com domain.

    Final code

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
    import requests
    import re
    import os
    from urllib.parse import urlparse
    from urllib.request import urlopen
    
    file = open("index.md", "r")
    line = file.read()
    
    results = re.findall(r'(https?://[^\s]+)', line)
    
    for result in results:
    
    	# Filter only Wordpress domains
    	if "wordpress.com" in result:
    		# print(result)
    		# Create folder
    		if not os.path.exists("images"):
    			os.makedirs("images")
    
    		# Remove unnecessary characters in the URL
    		indexOfPng = result.find("?")
    		updatedResult = result[:indexOfPng]
    		# print(updatedResult)
    
    		# Get the filename
    		parse = urlparse(result)
    		filename = os.path.basename(parse.path)
    
    		# Create a file path
    		filePath = os.path.join("images", filename)
    		print(filePath)
    
    		request = requests.get(updatedResult)
    		with open(filePath, "wb") as file:
    			file.write(request.content)
    
    
  • SwiftUI: How to use NavigationStack inside the .toolbar

    First, why do I mostly find a solution after posting a Stackoverflow question?

    So most examples and tutorials only use NavigationStack and NavigationLink inside a List. I’m surprised by how nobody seems to implement using the .toolbar modifier.

    The solution was a little straightforward I found out.

    Instead of NavigationView, you use the new and shiny NavigationStack. And use .navigationDestination() instead of NavigationLink.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    @State private var goToSettings = false
    
    NavigationStack {
       ZStack {
          // Some more codes
       }
       .toolbar {
          Button(role: .destructive, action: {
             goToSettings = true
          }) {
             Label("Settings", systemImage: "gearshape.fill").foregroundColor(colorForeground)
          }
       }
       .navigationDestination(isPresented: $goToSettings, destination: {
           SettingsView()
        })
    }
    

    I need to get more familiar with SwiftUI’s modifiers as it is still a little confusing for me.

  • BuildException Error

    Updated last night to the latest Android Studio Dolphin build but encountered this annoying build error from the JetBrains IDE.

    Invalidating cache and restarting seems to not work. Jetbrains seems to be clueless too.

    This bug has already taken much of my time. This is annoying.

    Edit: September 17 at 9:14PM

    It seems that the error went away after declining the “standalone script” option in Android Studio.

  • Legacy Android Library - Networking Valley

    7 years ago I open-sourced my first Android library on GitHub.

    And shared it to my fellow Android devs in Stackoverflow’s Android channel.

    This is for sure one of my favorite highlights of my career.

  • Mixpanel Code Refactor

    Our app uses Mixpanel for analytical and strategic purposes. This post is about my refactoring journey.

    Mixpanel introduced another parameter in their getInstance() method called trackAutomaticEvents which is of a boolean data type. Now, this is the part that I didn’t anticipate because located throughout our codebase is this single line.

    1
    
    MixpanelAPI.getInstance(this, Constants.MIXPANEL_TOKEN).track("Log In")
    

    But right now it is broken since the proper way to implement on the new version is this.

    1
    
    MixpanelAPI.getInstance(this, Constants.MIXPANEL_TOKEN, true).track("Log In")
    

    Behold them compile errors.

    You might think why not use the Find All option in Android Studio? I could, but this is to future proof our codebase in case another getInstance() refactor in the future.

    Kotlin Object

    For the Kotlin singleton pattern, I always use object since they are good for one-time use purposes.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    import android.content.Context
    import com.mixpanel.android.mpmetrics.MixpanelAPI
    
    object Mixpanel {
    
        fun track(context: Context, track: String) {
            return MixpanelAPI.getInstance(context, Constants.MIXPANEL_TOKEN, true)
                .track(track)
        }
    }
    

    Now that I have isolated or “wrapped” in a separate class, I can now implement condition statements inside it. Most of my Mixpanel method calls were wrapped in a !isDebuggable condition and are sprinkled everywhere in our codebase. Not good.

    1
    2
    3
    4
    
    val isDebuggable = 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
    if (!isDebuggable) {
         MixpanelAPI.getInstance(this@MainActivity, Constants.MIXPANEL_TOKEN).track("Session - Employer")
    }
    

    Code Refactor

    Now, that I have a separate object class, I can just call the condition there instead.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    import android.content.Context
    import android.content.pm.ApplicationInfo
    import com.mixpanel.android.mpmetrics.MixpanelAPI
    
    object Mixpanel {
    
        fun track(context: Context, track: String) {
            val isDebuggable = 0 != context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
            if (!isDebuggable) {
                MixpanelAPI.getInstance(context, Constants.MIXPANEL_TOKEN, true).track(track)
            }
        }
    }
    

    So from this code block

    1
    2
    3
    4
    
    val isDebuggable = 0 != context?.applicationInfo!!.flags and ApplicationInfo.FLAG_DEBUGGABLE
     if (!isDebuggable) {
          MixpanelAPI.getInstance(activity, Constants.MIXPANEL_TOKEN).track("Share Profile")
     }
    

    to

    1
    
    Mixpanel.track(requireActivity(), "Share Profile")
    

    Future Proof and Conclusion

    If Mixpanel decides to update their getInstance() method, then I only have to change that one line in my object Mixpanel. This code refactor has been a long time coming. I’m just glad that I was able to push this out of the way. Now, on to the next refactor.

  • My First Professional Android Project

    I found my first “professional” Android project while backing up data from my old laptop. I called it a “birthday reminder” and this project was the one I demoed to the company I sent my application to.

    Now, I am unsure if I got the gig because the Android team lead was amazed by the project I demoed or because I am the only applicant. Yes, I was the only applicant for the job interview. This was back in 2011.

    You can find the code here if you are curious about what a 2011 Android project looks like.

    To give you an overview, there was no Android Studio back then. You have to use Eclipse IDE and download the Android platform plugin.

    Based on this file’s timestamp I created the project precisely on #Fri Oct 14 09:22:04 CST 2011. In Philippine time that is October 15 at 9:22 PM.

    This project wasn’t even under any sort of version control. This project kicked off my Android development career as I was hired several weeks later.

  • Compiling Android on the Command Line

    First of all, I am on Manjaro Xfce environment for this example.

    We need to install Gradle. I tried to install Gradle using ArchLinux’s pacman but it was very outdated. So based on Gradle’s installation guide one option is to use a package manager like Sdkman.

    Let’s go ahead and install SDKMAN!.

    1
    2
    
    curl -s "https://get.sdkman.io" | bash
    source "$HOME/.sdkman/bin/sdkman-init.sh"
    

    After running SDKMAN!, close the current terminal and open a new one. Then run

    1
    
    sdk install gradle
    

    But when I try to run gradle or java, it will throw an error No Java found.

    1
    2
    3
    4
    
    ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
    
    Please set the JAVA_HOME variable in your environment to match the
    location of your Java installation.
    

    We need to install Java’s JDK.

    1
    
    sudo pacman -S jre-openjdk-headless jre-openjdk jdk-openjdk openjdk-doc openjdk-src
    

    At this point, running java should now work.

    1
    2
    3
    4
    
    java --version
    openjdk 18.0.2 2022-07-19
    OpenJDK Runtime Environment (build 18.0.2+9)
    OpenJDK 64-Bit Server VM (build 18.0.2+9, mixed mode)
    

    And running gradle should also work likewise.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    gradle -v
    Welcome to Gradle 7.5!
    
    Here are the highlights of this release:
     - Support for Java 18
     - Support for building with Groovy 4
     - Much more responsive continuous builds
     - Improved diagnostics for dependency resolution
    
    For more details see https://docs.gradle.org/7.5/release-notes.html
    
    
    ------------------------------------------------------------
    Gradle 7.5
    ------------------------------------------------------------
    
    Build time:   2022-07-14 12:48:15 UTC
    Revision:     c7db7b958189ad2b0c1472b6fe663e6d654a5103
    
    Kotlin:       1.6.21
    Groovy:       3.0.10
    Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
    JVM:          18.0.2 (Oracle Corporation 18.0.2+9)
    OS:           Linux 5.15.57-2-MANJARO amd64
    

    When you cd to your Android project, gradle should work.

    1
    
    gradle build
    
  • Legacy Projects Part 1

    Development Date: Last Quarter 2012

    This was one of my app ideas back in 2012 where users can save important notes. I developed this using Eclipse IDE with Android SDK. And the screenshots are from Google’s Nexus 7, still their best device.

  • SwiftUI Journey Part 11: Passing parameters

    Right now I am still figuring out how to pass a parameter from one View to another. I read I need to use @Binding but don’t know what it is. Or what differs it from @State. Currently, I am still studying @Binding.

    All the tutorials and Apple documentation look good but they did not include what to do with the compile error in PreviewProvider.

    Well, it seems PreviewProvider is separate and you can pass static variables different from the View class.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    import SwiftUI
    
    struct DetailsView: View {
        
        @Binding var url: String
        
        var body: some View {
            Text("Hello, World!")
        }
    }
    
    struct DetailsView_Previews: PreviewProvider {
        
        @State static var urlPreview: String = Urls.main.advanceSearch
        
        static var previews: some View {
            DetailsView(url: $urlPreview)
        }
    }
    

    Compiled successfully.

  • SwiftUI Journey Part 10: Settings

    I can’t believe how easy it is to implement a settings page in SwiftUI. For the UI, I only need 37 lines.

    Of course, this implementation won’t be accepted by the programming gods. Why you may ask. This is implemented in a static way. What if you want to turn every Text() background to Color.yellow. Then you will have to add the modifier background to every Text() view.

    Let’s make it dynamic.

    Ah much better.