-
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 awordpress.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
andNavigationLink
inside aList
. 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 shinyNavigationStack
. And use.navigationDestination()
instead ofNavigationLink
.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 calledtrackAutomaticEvents
which is of aboolean
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 myobject 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
orjava
, 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 theView
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 toColor.yellow
. Then you will have to add the modifier background to everyText()
view.Let’s make it dynamic.
Ah much better.