<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shubham</title>
    <description>The latest articles on DEV Community by Shubham (@shu8).</description>
    <link>https://dev.to/shu8</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%2F174850%2F1ccd39c2-faec-48f3-af94-6fc2869a5601.jpg</url>
      <title>DEV Community: Shubham</title>
      <link>https://dev.to/shu8</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shu8"/>
    <language>en</language>
    <item>
      <title>Wear OS apps with Flutter (4/4): Publishing &amp; Getting Approved on the Play Store</title>
      <dc:creator>Shubham</dc:creator>
      <pubDate>Sat, 16 Aug 2025 10:00:00 +0000</pubDate>
      <link>https://dev.to/shu8/wear-os-apps-with-flutter-44-publishing-getting-approved-on-the-play-store-5d00</link>
      <guid>https://dev.to/shu8/wear-os-apps-with-flutter-44-publishing-getting-approved-on-the-play-store-5d00</guid>
      <description>&lt;h2&gt;
  
  
  Publishing
&lt;/h2&gt;

&lt;p&gt;In the Play Store Console, we need to add 'Wear OS' as a &lt;em&gt;form factor&lt;/em&gt; of our app – until we do this, there will be no way to actually upload Wear OS screenshots or the actual Wear OS app! To do this, go to the "Test and Release" &amp;gt; "Advanced Settings" section from the left-navbar. On this page there will be a "Form factors" tab, from which you can choose "Add form factor" and choose Wear OS:&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%2Fash4y9nzzuf3cbvaygiy.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%2Fash4y9nzzuf3cbvaygiy.png" alt=" " width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this, you will also need to enrol and accept the Wear OS program conditions, which should appear after selecting the form factor.&lt;/p&gt;

&lt;p&gt;Once all that's done, publishing a Wear OS app is just like any other Android app: go to "Test and Release", choose the appropriate track (e.g., "Production") and click "Create new release" (make sure you've chosen "Wear OS only" next to the button):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tle48emj5hed72lyh08.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%2F7tle48emj5hed72lyh08.png" alt=" " width="800" height="42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, you can upload an App Bundle which you must first build using &lt;code&gt;flutter build appbundle&lt;/code&gt; and continue the steps to submit for review. Finally, you'll see a "Changed in review" screen like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63zyfqz7x7geqxeb0ee5.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%2F63zyfqz7x7geqxeb0ee5.png" alt=" " width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Google Play Console will run some automated checks to find 'common' issues that can be fixed before sending for manual review. This could potentially save days of review time if there's something they detect as being obviously wrong. However, I found it didn't quite detect everything you might expect would be easily identifiable (see below for more details).&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting approved
&lt;/h2&gt;

&lt;p&gt;There are some &lt;a href="https://developer.android.com/docs/quality-guidelines/wear-app-quality" rel="noopener noreferrer"&gt;relatively strict review guidelines for Wear OS apps&lt;/a&gt; which you need to satisfy before your app can be approved and published for end-users. I share some of the key ones that caught me out in this post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rejection #1: Play Store listing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Your play listing description doesn't mention Wear OS.&lt;/p&gt;
&lt;/blockquote&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%2Fwarceny4h0v1b2ehiu4o.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%2Fwarceny4h0v1b2ehiu4o.png" alt=" " width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;must&lt;/strong&gt; mention "Wear OS" in your Play Store listing's description. Not "WearOS", not "Android Wear' – otherwise, your app will likely be rejected and you'll have to go through the review process (which took 2-5 days each time for me!).&lt;/p&gt;

&lt;p&gt;The Google Play Console does have a series of automated checks they perform before sending it off for human review, so it seems like something that could easily be automated, but it wasn't in my case!&lt;/p&gt;

&lt;h3&gt;
  
  
  Rejection #2: Custom font sizes
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Your app must conform to the font size set by the user in System Settings. If the user selects a larger font size, ensure that text and controls are not cut off by screen edges.&lt;/p&gt;
&lt;/blockquote&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%2Fsre5y0xovyruknikvbt7.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%2Fsre5y0xovyruknikvbt7.png" alt=" " width="800" height="798"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkxnsu3xucl42uuei1jfx.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%2Fkxnsu3xucl42uuei1jfx.png" alt=" " width="800" height="744"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;must&lt;/strong&gt; respect and adjust according to the *font size *set on the device. I found this tricky to solve initially, because it was physically impossible to show the same content when the font is larger because everything takes up so much more space!&lt;/p&gt;

&lt;p&gt;In the end, I had to come to terms with showing less information on some screen sizes/font sizes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screen sizes
&lt;/h3&gt;

&lt;p&gt;Another useful guideline to call out is the screen size requirement. These days, it's already quite common for developers to test their apps on different devices and screen sizes (e.g., in web development, but even in Android app development it's becoming more important with different phones having different constraints like notches).&lt;/p&gt;

&lt;p&gt;The same goes for a Wear OS app, but is perhaps more important because the screen sizes are already so small! At a minimum, you should aim to test your app and UI fully on the 2 Wear OS emulators supported by Google: "Wear OS small round 1.2 inch (192dp)" and "Wear OS large round 1.39 inch (227dp)". It could also be useful to try on square devices (although, from what I've seen, it seems most modern Wear OS devices end up being circular).&lt;/p&gt;

&lt;h3&gt;
  
  
  Splash screen
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.android.com/design/ui/wear/guides/m2-5/behaviors-and-patterns/launch#branded" rel="noopener noreferrer"&gt;Your app must have a splash screen.&lt;/a&gt; The display requirements are also quite specific: "Use a black window background with the app icon. The app icon must be a 48x48dp circular icon that is positioned in the center of the watch face".&lt;/p&gt;

&lt;p&gt;To set up a working splash screen, I used the &lt;a href="https://pub.dev/packages/flutter_native_splash" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_native_splash&lt;/code&gt;&lt;/a&gt; package and set up my config in &lt;code&gt;pubspec.yaml&lt;/code&gt; as follows.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter_native_splash:
  color: "#000000" # Background color of the splash screen
  image: assets/icon/icon.png # Path to your logo or splash image
  android: true # Enable splash screen for Android
  android_gravity: center # Position of the image on Android (center, bottom, etc.)
  fullscreen: true # Enable full-screen mode (removes the status bar)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, you can run &lt;code&gt;dart run flutter_native_splash:create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I also had to add the following to &lt;code&gt;MainActivity.kt&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import androidx.core.splashscreen.SplashScreen
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
...
override fun onCreate(savedInstanceState: android.os.Bundle?) {
    installSplashScreen()
    super.onCreate(savedInstanceState)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and the following to &lt;code&gt;android/app/build.gradle.kts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    ...
    implementation("androidx.core:core-splashscreen:1.2.0-beta02")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After the above configurations, my splash screen loaded successfully!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>wearos</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Wear OS apps with Flutter (3/4): Using Platform Channels to implement custom functionality (location &amp; user text input)</title>
      <dc:creator>Shubham</dc:creator>
      <pubDate>Sat, 09 Aug 2025 10:00:00 +0000</pubDate>
      <link>https://dev.to/shu8/wear-os-apps-with-flutter-34-using-platform-channels-to-implement-custom-functionality-2c4g</link>
      <guid>https://dev.to/shu8/wear-os-apps-with-flutter-34-using-platform-channels-to-implement-custom-functionality-2c4g</guid>
      <description>&lt;p&gt;This is part 3 of a series on developing Wear OS apps with Flutter.&lt;br&gt;
Check out &lt;a href="https://dev.to__GHOST_URL__/wear-os-flutter-1/"&gt;part 1 for an introduction to Wear OS and common design guidelines&lt;/a&gt; and &lt;a href="https://dev.to__GHOST_URL__/wear-os-flutter-2"&gt;part 2 to setup and install your first Wear OS app&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Wear OS support in existing Flutter packages is limited. For example, if you want to get the device's location, the most common ways tend to be to install a package like &lt;a href="https://pub.dev/packages/location" rel="noopener noreferrer"&gt;&lt;code&gt;location&lt;/code&gt;&lt;/a&gt;or &lt;a href="https://pub.dev/packages/geolocator" rel="noopener noreferrer"&gt;&lt;code&gt;geolocator&lt;/code&gt;&lt;/a&gt;. However, both of these currently have &lt;a href="https://github.com/Baseflow/flutter-geolocator/issues/1197" rel="noopener noreferrer"&gt;known issues&lt;/a&gt; or &lt;a href="https://github.com/Lyokone/flutterlocation/issues/732" rel="noopener noreferrer"&gt;incompatibilities&lt;/a&gt; with Wear OS.&lt;/p&gt;

&lt;p&gt;It's still very easy to implement our own native code that can be called from Flutter, using &lt;a href="https://docs.flutter.dev/platform-integration/platform-channels" rel="noopener noreferrer"&gt;Platform Channels&lt;/a&gt;. With Flutter, it's always easy to rely on these abstractions that are provided for us, but Platform Channels are very useful to understand, because they are actually how all the packages we use are implemented under-the-hood – they just wrap platform-specific native code as a helpful API.&lt;/p&gt;

&lt;p&gt;In short, they provide a way for our Flutter code to call some native platform-specific code. That could mean we create, for example, a simple &lt;code&gt;getLocation&lt;/code&gt; function which in turn calls some *Kotlin *code on Android devices, some *Swift *code on iOS devices, and so on – all automatically, without us, as the Flutter developer, needing to worry about which device/OS/platform our app is running on.&lt;/p&gt;

&lt;p&gt;As we are creating a simplified Wear OS (Android)-only app in this case, we can implement just the Kotlin side of the Platform Channel as an example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Platform Channels for Android with Kotlin
&lt;/h2&gt;

&lt;p&gt;There are 2 parts to this: the native Android (Kotlin) side and the Flutter side (our 'app').&lt;/p&gt;

&lt;p&gt;In my app, there were 2 times in which I needed to make use of Platform Channels: to implement a &lt;code&gt;getLocation&lt;/code&gt; function and a &lt;code&gt;getUserTextInput&lt;/code&gt; function (to show the Wear OS keyboard so the user can type some short text).&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the device location
&lt;/h3&gt;

&lt;p&gt;Let's start with the Kotlin side of implementing a &lt;code&gt;getLocation&lt;/code&gt; function: we need to write the code that will actually be run that uses the Android SDKs, using Kotlin. We need to write this code in the &lt;code&gt;MainActivity.kt&lt;/code&gt; file in the &lt;code&gt;android/app/src/main/kotlin/com.mypackage.name&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;For location specifically, we can make use of the &lt;a href="https://developer.android.com/training/wearables/apps/location-detection" rel="noopener noreferrer"&gt;Fused Location Provider (FLP) API&lt;/a&gt;. This is ideal for use-cases where we just want to get the current location as a one-off (as opposed to continuously tracking it for activity tracking purposes – see the linked docs for details on how to implement that). FLP will automatically determine the best way to get the current location; for example, it might get the location from the connected phone if that's recent rather than wasting battery to use the watch's GPS.&lt;/p&gt;

&lt;p&gt;To use this API, we first need to define it as a dependency in our app – add the following to &lt;code&gt;android/app/build.gradle.kts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    implementation("com.google.android.gms:play-services-location:21.3.0")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Platform Channels are quite simple: we need to override a &lt;code&gt;configureFlutterEngine&lt;/code&gt; method and implement a method call handler. The method call handler takes a parameter that is the name of the 'method' we want to create. In our case, we can call it &lt;code&gt;getLocation&lt;/code&gt;, and this is what we will call from the Flutter-side too.&lt;/p&gt;

&lt;p&gt;The code to implement this might look like something below – remember to update the &lt;code&gt;CHANNEL&lt;/code&gt; variable, because this is used to define the 'name' of our Platform Channel.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainActivity : FlutterActivity() {
  private val CHANNEL = "com.mypackage.name"

  private lateinit var fusedLocationClient: FusedLocationProviderClient
  fun handleLocationSuccess(location: Location?, result: MethodChannel.Result) {
    location?.let {
      result.success(hashMapOf("latitude" to it.latitude, "longitude" to it.longitude))
    }
            ?: result.error("Failure", null, null)
  }

  fun handleLocationFailure(exception: Exception, result: MethodChannel.Result) {
    println(exception.message)
    result.error("Failure", exception.message, null)
  }

  fun getCurrentLocation(result: MethodChannel.Result) {
    fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
    fusedLocationClient.flushLocations()

    val priority = LocationRequest.PRIORITY_HIGH_ACCURACY
    val cancellationTokenSource = CancellationTokenSource()
    fusedLocationClient
            .getCurrentLocation(priority, cancellationTokenSource.token)
            .addOnSuccessListener { location: Location? -&amp;gt; handleLocationSuccess(location, result) }
            .addOnFailureListener { exception -&amp;gt; handleLocationFailure(exception, result) }
  }

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
            call,
            result -&amp;gt;
      if (call.method == "getLocation") {
        getCurrentLocation(result)
      } else {
        result.notImplemented()
      }
    }
  }

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

&lt;/div&gt;

&lt;p&gt;Finally, we can actually call this code from Flutter like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static const platform = MethodChannel('com.mypackage.name');
final coordsResult = await platform.invokeMethod('getLocation');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, &lt;code&gt;coordsResult&lt;/code&gt; will throw if our Platform Channel called &lt;code&gt;result.error&lt;/code&gt; at any time, or return whatever was passed to &lt;code&gt;result.success&lt;/code&gt; – in our case, a HashMap of &lt;code&gt;{latitude, longitude}&lt;/code&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting user text input via the Wear OS keyboard
&lt;/h3&gt;

&lt;p&gt;In Wear OS, getting user textual input isn't as simple as a normal Flutter app, where you might use a &lt;code&gt;TextField&lt;/code&gt; widget. Instead, Wear OS has it's own keyboard and UI for accepting user input – this page might look familiar to you:&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%2Fwtiw28bo2fky8ndcocn2.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%2Fwtiw28bo2fky8ndcocn2.png" alt="Search Page" width="454" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I couldn't find a built-in or community-built package to show this screen on Wear OS. But now that we have the base implementation of &lt;code&gt;configureFlutterEngine&lt;/code&gt;, it's actually quite trivial to add more methods. We just need to add another &lt;code&gt;else if&lt;/code&gt; for more names of our &lt;code&gt;call.method&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The way this would normally be shown in a vanilla native Android app is to call the &lt;a href="https://developer.android.com/reference/androidx/core/app/RemoteInput" rel="noopener noreferrer"&gt;&lt;code&gt;RemoteInput&lt;/code&gt;&lt;/a&gt; API. There's more information on the &lt;a href="https://developer.android.com/training/wearables/user-input/wear-ime" rel="noopener noreferrer"&gt;official docs on creating and using input editors&lt;/a&gt; in Wear OS too. To use this, we first need to add another dependency to our &lt;code&gt;android/app/build.gradle.kts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
  ...
  implementation("androidx.wear:wear-input:1.2.0-alpha02")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, we can implement a new &lt;code&gt;getText&lt;/code&gt; method in our method handler, like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
} else if (call.method == "getText") {
        val remoteInputs: List&amp;lt;RemoteInput&amp;gt; =
                listOf(RemoteInput.Builder(REMOTE_INPUT_KEY).setLabel("Enter text").build())
        val intent: Intent = RemoteInputIntentHelper.createActionRemoteInputIntent()
        RemoteInputIntentHelper.putRemoteInputsExtra(intent, remoteInputs)
        _inputResult = result
        startActivityForResult(intent, TEXT_INTENT_ID)
} else {
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This creates an Intent created by the &lt;code&gt;RemoteInput&lt;/code&gt; API to display the native input screen. We store the &lt;code&gt;result&lt;/code&gt; of the Platform Channel under &lt;code&gt;_inputResult&lt;/code&gt;, so that we still have a reference to it when the Intent returns the text entered by the user.&lt;/p&gt;

&lt;p&gt;We call &lt;a href="https://developer.android.com/reference/android/app/Activity#startActivityForResult(android.content.Intent,%20int)" rel="noopener noreferrer"&gt;&lt;code&gt;startActivityForResult&lt;/code&gt;&lt;/a&gt; with a 2nd 'request code' parameter &lt;code&gt;TEXT_INTENT_ID&lt;/code&gt;, which is an arbitrary number that we can define to uniquely identify the results of different activities in our app. This way, when various activities return, we can identify what each one is. We also define a &lt;code&gt;REMOTE_INPUT_KEY&lt;/code&gt; constant for the &lt;code&gt;RemoteInput&lt;/code&gt; so that we can extract the text entered later:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  private val TEXT_INTENT_ID = 1
  private val REMOTE_INPUT_KEY = "input"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we can listen for the result of the activity as follows, by using the &lt;code&gt;TEXT_INTENT_ID&lt;/code&gt; to identify the correct Intent result, and &lt;code&gt;REMOTE_INPUT_KEY&lt;/code&gt; to extract the text entered:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  override fun onActivityResult(requestCode: Int, result: Int, intent: Intent?) {
    if (requestCode != TEXT_INTENT_ID) return super.onActivityResult(requestCode, result, intent)

    if (result != Activity.RESULT_OK) {
      _inputResult.error("Error", "Error getting text", "")
      return
    }

    val inputResult = RemoteInput.getResultsFromIntent(intent)
    _inputResult.success(inputResult.getCharSequence(REMOTE_INPUT_KEY))
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, we can call our input screen in Flutter just as we did earlier, but invoking the &lt;code&gt;getText&lt;/code&gt; method this time!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static const platform = MethodChannel('com.mypackage.name');
final text = await platform.invokeMethod('getText');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;text&lt;/code&gt; will now contain anything passed to &lt;code&gt;inputResult.success&lt;/code&gt; above (in our case, the string contents that the user entered), or throw if we called &lt;code&gt;_inputResult.error&lt;/code&gt;!&lt;/p&gt;




&lt;p&gt;In the next part, we'll look at how to publish our Wear OS app on the Play Store, with tips on how to pass the Review process!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>wearos</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Wear OS apps with Flutter (2/4): Setting up, designing circular UIs &amp; running on an emulator/device</title>
      <dc:creator>Shubham</dc:creator>
      <pubDate>Sun, 03 Aug 2025 15:35:55 +0000</pubDate>
      <link>https://dev.to/shu8/wear-os-apps-with-flutter-24-setting-up-designing-circular-uis-running-on-an-emulatordevice-2ja5</link>
      <guid>https://dev.to/shu8/wear-os-apps-with-flutter-24-setting-up-designing-circular-uis-running-on-an-emulatordevice-2ja5</guid>
      <description>&lt;p&gt;A Wear OS app is just a 'normal' Android app (e.g., a &lt;code&gt;.APK&lt;/code&gt; file), but with access to some additional SDKs and functionality specific to the watch hardware.&lt;/p&gt;

&lt;p&gt;This is helpful because it means most of the time, you can follow typical Android instructions, including setting up an initial app!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up a template app
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;flutter create wear_os_app&lt;/code&gt; in your terminal, or use the &lt;code&gt;Flutter: New Project&lt;/code&gt; command in VS Code (Ctrl/Cmd+Shift+P)&lt;/p&gt;

&lt;p&gt;This will create a boilerplate 'counter' app in Flutter called &lt;code&gt;wear_os_app&lt;/code&gt;. Now we just need to configure a setting to define our Wear OS app as a &lt;em&gt;**standalone *&lt;/em&gt;*app: edit the manifest file at &lt;code&gt;android\app\src\main\AndroidManifest.xml&lt;/code&gt; and insert the following &lt;code&gt;&amp;lt;meta-data&amp;gt;&lt;/code&gt; block into the existing &lt;code&gt;&amp;lt;application&amp;gt;&lt;/code&gt; block:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;application&amp;gt;
  ...
  &amp;lt;meta-data
    android:name="com.google.android.wearable.standalone"
    android:value="true"
    /&amp;gt;
  ...
&amp;lt;/application&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can also optionally install some packages that have been created for Wear OS in Flutter specifically. For example, &lt;a href="https://pub.dev/packages/wear_plus" rel="noopener noreferrer"&gt;&lt;code&gt;wear_plus&lt;/code&gt;&lt;/a&gt; exposes new widgets called &lt;code&gt;AmbientMode&lt;/code&gt; and &lt;code&gt;WatchShape&lt;/code&gt; that let you detect whether the device is in ambient mode and differentiate between circular/square frames.&lt;/p&gt;

&lt;p&gt;Note &lt;a href="https://support.google.com/googleplay/android-developer/answer/11926878#:~:text=migration%20guide.-,Wear%20OS%20app%20requirements,-Android%20OS%20version" rel="noopener noreferrer"&gt;from 31st August 2025, the minimum target API level for Wear OS apps will be Android 14 (API Level 34)&lt;/a&gt;. To avoid issues with future updates and Play Store approvals, it's best to change your target level as early as possible. It also gives you a chance to ensure you don't accidentally start to rely on some APIs that are going to be immediately deprecated!&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the app on an Emulator
&lt;/h2&gt;

&lt;p&gt;We can already run this as a Wear OS App in an Emulator. First, we need to create a Wear OS device emulator in the Android Device Manager: open Android Studio, click "&lt;em&gt;More Actions&lt;/em&gt;" &amp;gt; "&lt;em&gt;Virtual Device Manager&lt;/em&gt;" &amp;gt; "&lt;em&gt;+&lt;/em&gt;" &amp;gt; choose "&lt;em&gt;Wear OS&lt;/em&gt;" Form Factor.&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%2Futl4np0u853kle83b38i.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%2Futl4np0u853kle83b38i.png" alt="Android Studio homepage 'More Actions' dropdown" width="474" height="459"&gt;&lt;/a&gt;&lt;br&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%2Fynj27cvmwgi7mwcbuuva.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%2Fynj27cvmwgi7mwcbuuva.png" alt="Android Studio Device Manager 'add device' window" width="449" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: if you can't see the "&lt;em&gt;Wear OS&lt;/em&gt;" form factor, make sure you are running the "&lt;em&gt;Android Studio Meerkat Feature Drop&lt;/em&gt;" version and in "&lt;em&gt;SDK Manager&lt;/em&gt;" (under "&lt;em&gt;More Actions&lt;/em&gt;" again), ensure you have installed the "&lt;em&gt;Wear OS 5.1 Intel x86_64 Atom System Image&lt;/em&gt;" with API Level &lt;code&gt;35-ext15&lt;/code&gt; (or the version appropriate for your system architecture e.g., ARM):&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%2Fqcxeuxbzltma0emlz1v0.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%2Fqcxeuxbzltma0emlz1v0.png" alt="Android Studio SDK Manager window" width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we can start the emulator, and run our app in it with &lt;code&gt;flutter run&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3n2ydwaxm7x5ng74lxoj.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%2F3n2ydwaxm7x5ng74lxoj.png" alt="Screenshot of Emulator screen running app" width="454" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see, there's still a bit of work we need to do to optimise it for a smaller circular display, but it works!&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing the layout
&lt;/h2&gt;

&lt;p&gt;We can make some initial minor tweaks to make this look better on a smartwatch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Removing the &lt;code&gt;AppBar&lt;/code&gt;.&lt;/strong&gt; The default counter app comes with a &lt;code&gt;Scaffold&lt;/code&gt; and &lt;code&gt;appBar&lt;/code&gt; property (which defines the "&lt;em&gt;Flutter Demo Home Page&lt;/em&gt;" we see clipped off in the image above). Due to the limited space on smartwatches, we don't usually see this kind of UI element in most apps, and should usually be safe to remove.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Adding padding.&lt;/strong&gt; On circular devices, the screen is actually &lt;em&gt;clipped&lt;/em&gt; around the circumference, which means some content ends up being hidden! Whilst there are some clever methods of wrapping content within a circular container (e.g., see this great Stack Overflow answer: &lt;a href="https://stackoverflow.com/a/71895002" rel="noopener noreferrer"&gt;Wrapping text inside a circle (Flutter)&lt;/a&gt;), I found that for simpler apps, padding can get you quite far, especially for &lt;strong&gt;primarily centered content&lt;/strong&gt;. For example, wrapping all your content in a &lt;code&gt;Padding&lt;/code&gt; with a padding of &lt;code&gt;EdgeInsets.fromLTRB(10, 5, 10, 5)&lt;/code&gt; and a &lt;code&gt;Center&lt;/code&gt; child avoids needing to touch the borders in most cases!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Changing the FAB (floating action button)&lt;/strong&gt;. By default, the FAB is placed at the bottom right, but the placement and sizing can easily be tweaked with &lt;code&gt;floatingActionButtonLocation&lt;/code&gt;. I found that using &lt;code&gt;FloatingActionButtonLocation.miniCenterDocked&lt;/code&gt; worked quite well.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What these changes might look like in code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: &amp;lt;Widget&amp;gt;[
              const Text(
                'You have pushed the button this many times:',
                textAlign: TextAlign.center,
              ),
              Text(
                '$_counter',
                style: Theme.of(context).textTheme.headlineMedium,
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        mini: true,
        child: const Icon(Icons.add),
      ),
      floatingActionButtonLocation:
          FloatingActionButtonLocation.miniCenterDocked,
    );
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With just these tweaks we can get to layout that looks like below:&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%2Fpfo6j5lw38q9siynw1bc.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%2Fpfo6j5lw38q9siynw1bc.png" alt="Screenshot of emulator screen showing updated UI" width="454" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the app on an actual Wear OS Smartwatch
&lt;/h2&gt;

&lt;p&gt;If you have a physical smartwatch, it's also easy to run the app on that too! With usual Android phone apps, the most common and straightforward way is to connect a phone via USB. That's not an option for (most) Wear OS devices, but they do support Wireless Debugging (as do most Android phones too).&lt;/p&gt;

&lt;p&gt;Enabling 'developer mode' on Wear OS watches is similar to how we do it on Android phones: go to &lt;em&gt;Settings&lt;/em&gt; &amp;gt; &lt;em&gt;System *(or *Other&lt;/em&gt;) &amp;gt; &lt;em&gt;About (watch)&lt;/em&gt; &amp;gt; &lt;em&gt;Versions *(or '*Other version info&lt;/em&gt;'), and tap on the '&lt;em&gt;Build number&lt;/em&gt;' seven times. Once done, you'll see a toast message confirming developer mode is activated.&lt;/p&gt;

&lt;p&gt;You can now find these settings in &lt;em&gt;Settings *&amp;gt; *System *(or *Other&lt;/em&gt;) &amp;gt; &lt;em&gt;Developer Options&lt;/em&gt;. From here, you can enable "&lt;em&gt;Wireless debugging&lt;/em&gt;" (this requires your watch to be connected to a Wi-Fi network – in my experience, toggling "wireless debugging" on will auto-connect to a saved Wi-Fi network in a few seconds):&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%2F2ik1ehycutq4rwotv8lu.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%2F2ik1ehycutq4rwotv8lu.png" alt="Screenshot of Wear OS 'wireless debugging' page" width="466" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll be shown your watch's local IP address and a port to connect to.&lt;/p&gt;

&lt;p&gt;If you're connecting a computer for the first time, you'll first need to &lt;em&gt;pair&lt;/em&gt; it: tap the "&lt;em&gt;Pair new device&lt;/em&gt;" button. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxei9s2seydp0nl46swhf.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%2Fxei9s2seydp0nl46swhf.png" alt="Screenshot of Wear OS 'pair new device' button" width="466" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcuh6cc4ifvrlhg6hgu3.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%2Fkcuh6cc4ifvrlhg6hgu3.png" alt="Screenshot of Wear OS 'pair with device' screen showing pairing code" width="466" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you can pair ADB from your computer (on the same Wi-Fi network) – be sure to change the IP address and port as shown on the "pair with device" screen!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adb pair 192.168.68.102:44841
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After pairing, or if you've previously paired your computer, you can connect:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adb pair 192.168.68.102:35209
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note that this is a &lt;strong&gt;different&lt;/strong&gt; port to the 'pairing' port! It will also change every time you turn Wireless Debugging on!&lt;/p&gt;

&lt;p&gt;I'd recommend turning Wireless Debugging off whenever you finish, to conserve battery on your watch.&lt;/p&gt;

&lt;p&gt;Finally, to run your new Wear OS Flutter app on your physical watch, you can use &lt;code&gt;flutter run&lt;/code&gt; – you may be prompted to choose which device to run the app on, if you have multiple (e.g., the physical one and emulator).&lt;/p&gt;

&lt;h2&gt;
  
  
  A note about performance
&lt;/h2&gt;

&lt;p&gt;I found my app seemed to be quite slow when I was developing it and testing it like this. It got me quite worried because I wasn't doing anything super intensive, and I wasn't sure how I could optimise it.&lt;/p&gt;

&lt;p&gt;But it turns out it was only slow when it was being built in debug mode – building it &lt;strong&gt;for release&lt;/strong&gt; made the app much snappier. I found the slowness in debug mode was much more compared to the slowness you might expect when developing a typical Android phone app and testing it in debug mode, so this is useful to bear in  mind!&lt;/p&gt;

&lt;p&gt;Towards the end of this blog series, I will explain how we can build apps in release mode, ready for testing and/or publishing to the Play Store.&lt;/p&gt;




&lt;p&gt;In the next part, we'll implement a Platform Channel to use native device functionality that may not be supported in typical Flutter packages (e.g., getting device location, or getting user input)!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>wearos</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Wear OS apps with Flutter (1/4): Introduction</title>
      <dc:creator>Shubham</dc:creator>
      <pubDate>Sat, 19 Jul 2025 23:24:59 +0000</pubDate>
      <link>https://dev.to/shu8/wear-os-apps-with-flutter-14-introduction-3a63</link>
      <guid>https://dev.to/shu8/wear-os-apps-with-flutter-14-introduction-3a63</guid>
      <description>&lt;p&gt;I &lt;a href="https://play.google.com/store/apps/details?id=uk.co.intersectsoftware.bustrackr" rel="noopener noreferrer"&gt;recently published my first Wear OS app&lt;/a&gt;, but I found resources around developing smartwatch apps, particularly for Wear OS (Android) to be quite lacking. This series of blog posts will share how you can create full-fledged Wear OS apps using Flutter, that are production-ready and published to Play Store for end-users; I hope it can help others bring their own ideas to life as smartwatch apps, and get an idea of the process involved perhaps even before starting!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Introduction to Wear OS, its design guidelines, and other considerations&lt;/li&gt;
&lt;li&gt;Setting up a Flutter Wear OS app, improving the UI for circular screens, and running it on an emulator and/or physical device&lt;/li&gt;
&lt;li&gt;Using Platform Channels to replace common Flutter packages which aren't yet compatible with Wear OS&lt;/li&gt;
&lt;li&gt;Getting approved and published on the Play Store&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before we start, I do want to call out that whilst Flutter is best known and most useful for creating &lt;em&gt;cross-platform&lt;/em&gt; apps, this series assumes we are creating a simple &lt;strong&gt;standalone Wear OS&lt;/strong&gt; app &lt;em&gt;only&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You might question what is the point of creating &lt;em&gt;just&lt;/em&gt; a Wear OS app in Flutter, but if Flutter is something you're already comfortable with for Android/iOS/Web apps, it's likely much faster to continue to use Flutter. You can also re-used any brand components and styles with ease, as opposed to migrating to e.g., Kotlin entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;To start with, here are a few core concepts that are useful to understand before developing a Wear OS app. Some of these might decide the direction in which you decide to go!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://developer.android.com/training/wearables/apps/standalone-apps" rel="noopener noreferrer"&gt;&lt;strong&gt;Standalone vs non-standalone Wear OS apps&lt;/strong&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A 'standalone' app is one that can run independently of a connected phone.&lt;/p&gt;

&lt;p&gt;This was a little confusing at first, because the majority of Wear OS smartwatches don't have cellular capabilities, so does that mean they are 'dependent' on a connected phone? No – 'dependent' in this context means that all the app's functionality must be able to be completed entirely on the watch without &lt;em&gt;the user&lt;/em&gt; interacting with their phone.&lt;/p&gt;

&lt;p&gt;For example, if you require users to set up the watch app, or configure settings, through their phone via a companion app, then it is a &lt;em&gt;non-standalone&lt;/em&gt; app. Conversely, if you have a settings page within the watch, and the user doesn't need to touch their phone, then you have a *standalone *app.&lt;/p&gt;

&lt;p&gt;Crucially, either type of app will still be able to use the connected phone for Internet access – so if your standalone app uses e.g., an online API to surface data to a user, this is fine! And if you're using a watch with cellular capability, then it won't even need to be connected to the phone.&lt;/p&gt;

&lt;p&gt;In this blog series, we focus on creating a &lt;em&gt;standalone&lt;/em&gt; app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internet capabilities
&lt;/h3&gt;

&lt;p&gt;Smartwatches prioritise using Bluetooth Low Energy (BLE) for communication (if you're interested in how BLE works and implementing your own BLE services on e.g., Raspberry Pis, &lt;a href="https://dev.to__GHOST_URL__/ble-rpi-pico-provision-wifi-1/"&gt;check out my other blog series&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is because BLE is much less power-hungry than Wi-Fi and Cellular data. This also means that even if your standalone watch app is accessing the Internet, the requests may still be going to and from your phone via Bluetooth (where the phone then connects to the Internet via Wi-Fi/mobile data).&lt;/p&gt;

&lt;p&gt;This is something to bear in mind because your watch app might end up having a &lt;a href="https://developer.android.com/training/wearables/apps/standalone-apps#necessary-data" rel="noopener noreferrer"&gt;relatively small bandwidth of ~4KB/s&lt;/a&gt;. This can introduce latency, memory, or battery usage.&lt;/p&gt;

&lt;p&gt;Ideally, Wear OS apps should be snappy and quick to use, so this can be an important factor if you are trying to do data-intensive things, or even load pictures/icons from the Internet!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://developer.android.com/training/wearables/apps/power" rel="noopener noreferrer"&gt;Battery&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;One of the biggest pain points of modern smartwatches is their battery life. The majority are still at ~1 day battery life (save some, like the OnePlus Watch 2 and 3, which I'd highly recommend!).&lt;/p&gt;

&lt;p&gt;This means we need to be wary about the resources we use in our app, for example by reducing animation and memory-intensive processes.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.android.com/training/wearables/apps/power" rel="noopener noreferrer"&gt;official docs outline many areas to look out for &lt;/a&gt;when considering battery usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design
&lt;/h3&gt;

&lt;p&gt;Smartwatch screens are very small, so need to be designed with that in mind. The UI and UX considerations are vastly different to a typical mobile app, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Buttons should have quite a large tap surface&lt;/li&gt;
&lt;li&gt;Text should be relatively large and glanceable&lt;/li&gt;
&lt;li&gt;Loading times should be minimal (nobody wants to stare at their wrist just waiting for something!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another challenge I faced was figuring out exactly how to portray the information I wanted to in such a small screen. Taking into account different screen sizes, and even different font sizes, means that you also need to adapt the information &lt;em&gt;density&lt;/em&gt; for different users. That might mean showing one less piece of information, or prioritising functional UI elements over aesthetic ones.&lt;/p&gt;




&lt;p&gt;In the next part, we'll set up a sample Flutter project and get it running on a Wear OS device!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>wearos</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Bring the 'jump to search' keyboard shortcut to any site with userscripts</title>
      <dc:creator>Shubham</dc:creator>
      <pubDate>Tue, 11 Jul 2023 16:24:09 +0000</pubDate>
      <link>https://dev.to/shu8/bring-the-jump-to-search-keyboard-shortcut-to-any-site-with-userscripts-518d</link>
      <guid>https://dev.to/shu8/bring-the-jump-to-search-keyboard-shortcut-to-any-site-with-userscripts-518d</guid>
      <description>&lt;p&gt;Some sites allow you to jump to their search bar using the &lt;code&gt;/&lt;/code&gt; key (e.g., Google, GitHub, and many more). However, for sites without such a shortcut (e.g., Gmail, Bing), it can feel quite tedious to have to scroll, find, and click the search bar each time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The power of userscripts
&lt;/h2&gt;

&lt;p&gt;This is where &lt;a href="https://en.wikipedia.org/wiki/Userscript" rel="noopener noreferrer"&gt;userscripts&lt;/a&gt; come in: some JavaScript code that you can configure to run on any site – and they can be quite powerful! For example, &lt;a href="https://github.com/soscripted/sox" rel="noopener noreferrer"&gt;I created and maintain a userscript&lt;/a&gt; that adds a bunch of additional optional features to the Stack Exchange network of sites (e.g., Stack Overflow). These can range from simple manipulations like tweaking the colour of an element, to full-fledged features like adding a 'find and replace' function to the post editor.&lt;/p&gt;

&lt;p&gt;Userscripts are similar to browser extensions, but they can be much easier and quicker to customise to your liking – creating a userscript can take less than a minute, whereas setting up a full browser extension can take considerably more time, especially if you've never made one before. Userscripts are also inherently open-source, so it is easier to verify the code before you install them!&lt;/p&gt;

&lt;p&gt;Userscripts also have &lt;a href="https://www.tampermonkey.net/documentation.php" rel="noopener noreferrer"&gt;some special APIs&lt;/a&gt; that let you fetch resources, interact with tabs, save data, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  The userscript metadata (header) block
&lt;/h2&gt;

&lt;p&gt;Every userscript must start with a 'header' called the &lt;em&gt;&lt;a href="https://wiki.greasespot.net/Metadata_Block" rel="noopener noreferrer"&gt;metadata block&lt;/a&gt;&lt;/em&gt;. This is just a set of JavaScript comments, each starting with a special key-value pair. For example, &lt;code&gt;// @name My Userscript&lt;/code&gt; defines the &lt;code&gt;@name&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;The header section must start and end with the special comments &lt;code&gt;// UserScript&lt;/code&gt; and &lt;code&gt;// /UserScript&lt;/code&gt; respectively.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://www.tampermonkey.net/documentation.php" rel="noopener noreferrer"&gt;many keys&lt;/a&gt; that can be used in the metadata block. One of the most important ones is &lt;code&gt;@match&lt;/code&gt; (and also &lt;code&gt;@include&lt;/code&gt; – see &lt;a href="https://stackoverflow.com/q/31817758" rel="noopener noreferrer"&gt;What is the difference between @include and @match in userscripts?&lt;/a&gt;). This lets you specify which page(s) to run a script on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interacting with the Document Object Model (DOM)
&lt;/h2&gt;

&lt;p&gt;One of the main things you usually do with a userscript is interact with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model" rel="noopener noreferrer"&gt;Document Object Model (DOM)&lt;/a&gt;. This represents the HTML page as a tree with nodes that can be manipulated via the JavaScript DOM API.&lt;/p&gt;

&lt;p&gt;For example, you can &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_object_model/Locating_DOM_elements_using_selectors" rel="noopener noreferrer"&gt;query for elements with &lt;code&gt;Document.querySelector()&lt;/code&gt;&lt;/a&gt;, change their &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style" rel="noopener noreferrer"&gt;style by setting the &lt;code&gt;HTMLElement.style&lt;/code&gt; property&lt;/a&gt;, add &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" rel="noopener noreferrer"&gt;event listeners with &lt;code&gt;EventTarget.addEventListener&lt;/code&gt;&lt;/a&gt; to detect clicks/hovers/etc, and much more!&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing the &lt;code&gt;/&lt;/code&gt; shortcut to any site
&lt;/h2&gt;

&lt;p&gt;With some basic knowledge of the DOM API, we can now easily bring the &lt;code&gt;/&lt;/code&gt; shortcut to any site by creating a small userscript:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install a userscript manager.&lt;br&gt;&lt;br&gt;
Userscript managers let you see all your userscripts in one place, and most have a built-in web editor so you can edit scripts within your browser. &lt;a href="https://www.tampermonkey.net/" rel="noopener noreferrer"&gt;Tampermonkey&lt;/a&gt; is a popular userscript manager, and is available for almost all browsers.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new userscript.&lt;br&gt;&lt;br&gt;
There will be a new extension button for your userscript manager with an option similar to "Create a new script":&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%2Fwobp0caftnsehh6j2hy3.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%2Fwobp0caftnsehh6j2hy3.png" alt="Tampermonkey browser extension 'Create a new script' button" width="257" height="289"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write your userscript code:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ==UserScript==
// @name         '/' search bar focus
// @description  Focus search bar on SLASH press
// @version      0.1
// @match        *://*/*
// ==/UserScript==

(function() {
    window.addEventListener("keypress", e =&amp;gt; {
        if (['TEXTAREA', 'INPUT'].includes(e.target.nodeName)) return;
        if (e.key !== "/") return;

        const searchField = document.querySelector('textarea[type="search"], input[type="search"], input[name="search"], input[placeholder*="Search"]');
        if (!searchField) return;

        searchField.focus();
        searchField.setSelectionRange(searchField.value.length, searchField.value.length);
        e.preventDefault();
    });
})();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how the metadata block uses &lt;code&gt;// @match *://*/*&lt;/code&gt;. This is because we want this particular script to run on &lt;em&gt;every&lt;/em&gt; page!&lt;/p&gt;

&lt;p&gt;After the metadata block, we have our actual code. Tampermonkey by default suggests wrapping your code into an &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE" rel="noopener noreferrer"&gt;IIFE (Immediately Invoked Function Expression)&lt;/a&gt;. This is generally a good practice as it means we do not pollute the global namespace with our own variables, functions, etc.&lt;/p&gt;

&lt;p&gt;The code adds a &lt;code&gt;keypress&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" rel="noopener noreferrer"&gt;event listener&lt;/a&gt; to the entire page. If the keypress is already within a &lt;code&gt;textarea&lt;/code&gt; or &lt;code&gt;input&lt;/code&gt; element, or is not the &lt;code&gt;/&lt;/code&gt; key, the handler immediately returns. Otherwise, the first search field is found, focussed, and the cursor is moved to the end.&lt;/p&gt;

&lt;p&gt;You can choose how you define a 'search' field, but some common aspects are that they have a &lt;code&gt;type=search&lt;/code&gt; or &lt;code&gt;name=search&lt;/code&gt; attribute. You can even use some more advanced &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors" rel="noopener noreferrer"&gt;CSS attribute selectors&lt;/a&gt; like &lt;code&gt;[attr*=value]&lt;/code&gt; to find elements whose attribute just &lt;em&gt;contains&lt;/em&gt; a string (e.g., containing "search" somewhere in the &lt;code&gt;placeholder&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's it! To test it out, you can try making e.g., &lt;a href="https://www.bing.com/search?q=test" rel="noopener noreferrer"&gt;a Bing search&lt;/a&gt; and pressing the &lt;code&gt;/&lt;/code&gt; key – you'll jump straight to the search bar!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>productivity</category>
      <category>browser</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How does Linux work?</title>
      <dc:creator>Shubham</dc:creator>
      <pubDate>Mon, 20 Sep 2021 18:22:01 +0000</pubDate>
      <link>https://dev.to/shu8/how-does-linux-work-55d5</link>
      <guid>https://dev.to/shu8/how-does-linux-work-55d5</guid>
      <description>&lt;p&gt;I recently prepared for a position which prompted me to read up and consolidate my knowledge on Operating Systems and Linux which led me down a weird and wonderful rabbit hole learning so much more than I thought I knew!&lt;/p&gt;

&lt;p&gt;This post intends to be a summary of how an Operating System (focussing on Linux) works, and what happens behind the scenes.&lt;/p&gt;

&lt;p&gt;So, let's get started!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I, myself, am constantly learning more about this area, and whilst I've tried to be as accurate and concise as possible, there may be mistakes. If you spot something that looks wrong, I would &lt;em&gt;love&lt;/em&gt; to hear from you in the comments so we can all learn together!&lt;/p&gt;

&lt;h2&gt;
  
  
  What are UNIX and Linux?
&lt;/h2&gt;

&lt;p&gt;UNIX and Linux are both families of Operating Systems (OSes) -- the underlying software behind many popular Operating Systems. An examples of a UNIX OSes is Solaris; examples of Linux-based distributions are Ubuntu and Arch Linux.&lt;/p&gt;

&lt;p&gt;The biggest difference between the two is that Linux is &lt;strong&gt;open-source&lt;/strong&gt; and &lt;strong&gt;free to use&lt;/strong&gt; – &lt;em&gt;you&lt;/em&gt; can contribute to it if you want! UNIX on the other hand is proprietary software which requires a license to use.&lt;/p&gt;

&lt;p&gt;There's also &lt;strong&gt;POSIX&lt;/strong&gt;: the Portable Operating System Interface, which defines a set of standards so different OSes can be compatible. It includes things like; what should program exit codes be? What default environment variables are there? How do filenames work? What is the underlying C API? A lot of Linux distributions are mostly POSIX-compliant (although they may not be officially certified!).&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the "Kernel" and "Operating System"?
&lt;/h2&gt;

&lt;p&gt;The kernel is a &lt;strong&gt;part of&lt;/strong&gt; the Operating System; it's at the lowest software level of the OS to control access to the hardware, system resources, files, processes, system calls, and more! Without an OS, your computer can't really do anything.&lt;/p&gt;

&lt;p&gt;A nice way to look at the difference between the Kernel and the general Operating System is that the OS as a whole sits between a user and the software; the kernel sits between the software and the hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, how does my computer start?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  BIOS
&lt;/h3&gt;

&lt;p&gt;Your computer has a set of fixed instructions in a specific physical memory location in ROM (Read-Only Memory), which usually form the &lt;strong&gt;BIOS&lt;/strong&gt; (Basic Input/Output System). This is firmware (low-level software that's permanent on your system) that initializes the hardware on boot.&lt;/p&gt;

&lt;p&gt;The BIOS usually performs a &lt;strong&gt;POST&lt;/strong&gt; (Power-On Self-Test) to detect and setup any connected hardware (e.g. memory, video cards, CPUs, etc.). If there's an error here, your computer will normally display some text (if it can), or perform various different audible beeps, with each different number of beeps indicating a specific problem.&lt;/p&gt;

&lt;p&gt;Once the hardware is confirmed to be working, the BIOS starts the &lt;strong&gt;boot&lt;/strong&gt; process which involves finding the boot device (e.g., hard drive). Boot devices usually store a &lt;strong&gt;bootloader&lt;/strong&gt; (or a pointer to it), in the &lt;em&gt;Master Boot Record&lt;/em&gt; (MBR) or in a specific partition on the drive (&lt;em&gt;EFI&lt;/em&gt;). This is a tiny piece of software which is less than a kilobyte in size, and is responsible for loading the OS into RAM (memory). An example of a bootloader is &lt;a href="https://www.gnu.org/software/grub/" rel="noopener noreferrer"&gt;GRUB&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initializing the Kernel
&lt;/h3&gt;

&lt;p&gt;Once the bootloader has been loaded, it needs to be executed! This can get quite complicated and I definitely do not know every single thing that happens here. Here is an overview of what now happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The kernel is decompressed.&lt;/li&gt;
&lt;li&gt;A few &lt;strong&gt;registers&lt;/strong&gt; are initialized (e.g. the Interrupt Handler Table and Global Descriptor Table) – these are needed later on when using the system.&lt;/li&gt;
&lt;li&gt;Various &lt;strong&gt;system calls&lt;/strong&gt; are made to spawn initial &lt;em&gt;processes&lt;/em&gt; such as the &lt;em&gt;task scheduler&lt;/em&gt; (these are all explained a bit later on!).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;init&lt;/strong&gt; process executes, which is responsible for mounting all file systems in read/write mode, starting &lt;em&gt;daemons&lt;/em&gt; (like &lt;code&gt;sshd&lt;/code&gt; for SSH connections, &lt;code&gt;httpd&lt;/code&gt; for HTTP connections, etc.), and calling the &lt;code&gt;getty&lt;/code&gt; program ("get &lt;a href="https://unix.stackexchange.com/questions/4126/what-is-the-exact-difference-between-a-terminal-a-shell-a-tty-and-a-con" rel="noopener noreferrer"&gt;TTY&lt;/a&gt;") which prompts you to log in. &lt;code&gt;systemd&lt;/code&gt; is a common &lt;em&gt;init&lt;/em&gt; process used in Linux distributions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point, your computer is up and running! Now what?&lt;/p&gt;

&lt;h2&gt;
  
  
  System Calls &amp;amp; CPU Execution Modes
&lt;/h2&gt;

&lt;p&gt;First, we should note that many of the low-level details about the hardware are abstracted away and hidden from user applications; this means the Operating System must issue requests to the kernel in the form of &lt;strong&gt;system calls&lt;/strong&gt; &lt;em&gt;(syscalls)&lt;/em&gt;, which are executed by the kernel.&lt;/p&gt;

&lt;p&gt;So, there are different CPU execution privilege modes (sometimes called &lt;em&gt;rings&lt;/em&gt;): User (ring 3) and Kernel (ring 0) mode. The rings in between are for device drivers (software that enables interaction with hardware peripherals).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User mode&lt;/strong&gt; is an &lt;em&gt;unprivileged&lt;/em&gt; mode for user programs – programs can run and execute code, but they can't manipulate actual memory, use input/output devices, nor switch modes itself. As a result, when any of these resources are needed, the programs send a &lt;em&gt;system call&lt;/em&gt; which generates a &lt;em&gt;software interrupt&lt;/em&gt;. This prompts the switch to Kernel mode where the kernel checks for permissions, performs necessary actions, and returns relevant data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kernel mode&lt;/strong&gt; is therefore a &lt;em&gt;privileged&lt;/em&gt; mode; there is unrestricted access to memory and devices. Any errors encountered here are critical and trigger a &lt;em&gt;kernel panic&lt;/em&gt; (analogous to a Windows Blue Screen of Death).&lt;/p&gt;

&lt;p&gt;Why have separate modes? Having a separate kernel mode ensures that programs can't interfere with each other; it is the single source of truth for the entire system, and it is more secure, as the kernel handles permission checks to resources!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Filesystem
&lt;/h2&gt;

&lt;p&gt;In Linux, you don't mount hard drives, or partitions. Instead, you mount the &lt;em&gt;file systems&lt;/em&gt; on those partitions.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Virtual File System&lt;/strong&gt; (VFS) abstracts a standard interface (think: an 'API') for file systems, so all file systems appear identical to the rest of the kernel and applications. Data is split into &lt;strong&gt;Blocks&lt;/strong&gt; (typically 4MB), which are further grouped into &lt;strong&gt;Block Groups&lt;/strong&gt;. The VFS caches blocks when they are accessed by placing them into the &lt;strong&gt;Buffer Cache&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inodes&lt;/strong&gt; (indexed nodes) are structures that store metadata for every file and directory, providing easy access to anyone who needs information on files. They have a number (&lt;em&gt;index&lt;/em&gt;) that uniquely identifies them in a filesystem, and are used in conjunction with the filesystem ID to ensure they are unique across the entire machine.&lt;/p&gt;

&lt;p&gt;Inodes are stored in a table so they are accessed by their index number, and they point to the disk blocks storing the contents of the file they represent.&lt;/p&gt;

&lt;p&gt;The use of inodes actually means &lt;em&gt;there is a limit to the number of files/directories you can store on a system&lt;/em&gt;! Mail servers can run into the problem where they store lots of tiny files (emails) which don't take up too much disk space but still run out of inodes! Inodes are usually 32-bit unsigned integers, meaning ~4.2 billion inodes maximum. Practically, a system might have much fewer available inodes as the default ratio tends to be 1 inode per &lt;em&gt;x&lt;/em&gt; bytes of storage capacity.&lt;/p&gt;

&lt;p&gt;Inodes store the file mode (permissions), type (file/directory), user, group, size, links, block count, creation/accessed/modified times, and inode checksum.&lt;/p&gt;

&lt;p&gt;Inodes don't store filenames themselves – why? These are stored in &lt;strong&gt;directory structures&lt;/strong&gt; (or 'directory entries', or 'dentries'). These are tables that store filenames and their corresponding inodes; the first two entries are always &lt;code&gt;.&lt;/code&gt; and &lt;code&gt;..&lt;/code&gt; which probably seem familiar. An advantage of this system is, if you are moving files, all you are doing is moving the &lt;code&gt;(name, inode)&lt;/code&gt; pair – so it's extremely cheap!&lt;/p&gt;

&lt;h3&gt;
  
  
  File permissions
&lt;/h3&gt;

&lt;p&gt;Commands like &lt;code&gt;chmod&lt;/code&gt; and &lt;code&gt;chown&lt;/code&gt; allow you to alter file &lt;strong&gt;permissions&lt;/strong&gt;, but how do they work? Each file/directory has &lt;strong&gt;three&lt;/strong&gt; user permission groups: &lt;em&gt;owner&lt;/em&gt;, &lt;em&gt;group&lt;/em&gt;, &lt;em&gt;all users&lt;/em&gt;(i.e., all &lt;em&gt;other&lt;/em&gt; users). For each of these, there are a further &lt;strong&gt;three&lt;/strong&gt; permission types: &lt;em&gt;read&lt;/em&gt;, &lt;em&gt;write&lt;/em&gt;, &lt;em&gt;execute&lt;/em&gt;. For directories, these mean slightly different things: listing the contents of the directory, changing the contents of the directory (new/delete/rename files), and moving into the directory respectively.&lt;/p&gt;

&lt;p&gt;So what do the permissions looks like? &lt;code&gt;_ rwx rwx rwx 1 owner:group&lt;/code&gt; is the general format! (Remember, this is all stored in the inode!) The first group of 3 is the &lt;em&gt;owner&lt;/em&gt; permissions, the second group is the &lt;em&gt;group&lt;/em&gt; permissions, and the final group is the &lt;em&gt;all users&lt;/em&gt; permissions. The final string shows which user/group owns the file.&lt;/p&gt;

&lt;p&gt;The very first character is the 'special file type flag': &lt;code&gt;_&lt;/code&gt; means no special permissions, &lt;code&gt;d&lt;/code&gt; means it is a directory, &lt;code&gt;l&lt;/code&gt; means it is a symbolic link, &lt;code&gt;s&lt;/code&gt; is the setuid/setgid permission for executables (meaning it should be executed with the owner permissions), and &lt;code&gt;t&lt;/code&gt; is the sticky bit permission (meaning only the file owner can rename or delete the directory).&lt;/p&gt;

&lt;p&gt;If you've ever used &lt;code&gt;chmod&lt;/code&gt; you may have used a &lt;em&gt;number&lt;/em&gt; to set permissions – this is the numeric method where a &lt;code&gt;4&lt;/code&gt; represents &lt;em&gt;read&lt;/em&gt;, &lt;code&gt;2&lt;/code&gt; represents &lt;em&gt;write&lt;/em&gt;, &lt;code&gt;1&lt;/code&gt; represents &lt;em&gt;execute&lt;/em&gt;. For example, &lt;code&gt;740&lt;/code&gt; means &lt;code&gt;7&lt;/code&gt; for the owner, &lt;code&gt;4&lt;/code&gt; for the group, &lt;code&gt;0&lt;/code&gt; for all others, i.e., &lt;code&gt;_ rwx r__ ___&lt;/code&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory
&lt;/h2&gt;

&lt;p&gt;Moving onto memory management! &lt;/p&gt;

&lt;p&gt;Linux is a multiprocessing OS; each process is a separate task with its own rights and responsibilities, and each has its own &lt;em&gt;virtual memory&lt;/em&gt;, running in its own &lt;em&gt;virtual address space&lt;/em&gt; so they can't affect each other, only interacting with others through the kernel.&lt;/p&gt;

&lt;p&gt;This means virtual and physical memory is split into &lt;strong&gt;pages&lt;/strong&gt;, small contiguous chunks of memory, which are mapped to each other via the &lt;strong&gt;page table&lt;/strong&gt;. When a program requests a virtual page address, the page table determines the actual memory address to use – if it's not found, you get a &lt;em&gt;page fault&lt;/em&gt;. Pages aren't always loaded, so &lt;em&gt;demand paging&lt;/em&gt; is where memory is loaded lazily, as it is needed.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;swap file&lt;/strong&gt; is a file on the disk that is used when a virtual page is needed, but no physical page is available. In this case, an existing page is written to disk to this swap file, to be re-loaded later if needed! &lt;em&gt;Thrashing&lt;/em&gt; occurs if pages are constantly being read from/written to, which means the OS can't actually do anything meaningful!&lt;/p&gt;

&lt;h2&gt;
  
  
  Processes
&lt;/h2&gt;

&lt;p&gt;Processes are computer programs in-action; they include program instructions, data, CPU registers, the Program Counter, and call stacks. There is a limited number of processes that can execute at one time. Processes are stored in a &lt;code&gt;task&lt;/code&gt; array in the form of a &lt;code&gt;task_struct&lt;/code&gt; data structure (think: a linked list of dictionaries). This stores lots of information like how virtual memory is mapped onto the system's physical memory; CPU time consumed; (effective) user/group IDs, etc.&lt;/p&gt;

&lt;p&gt;Every process (except for the initial &lt;code&gt;init&lt;/code&gt; process) has a parent – the &lt;code&gt;task_struct&lt;/code&gt; keeps a pointer to parent and child processes (a doubly linked list).&lt;/p&gt;

&lt;p&gt;Processes are &lt;strong&gt;not&lt;/strong&gt; &lt;em&gt;created&lt;/em&gt;, they are &lt;em&gt;cloned&lt;/em&gt; from a previous one via system calls. Usually, the &lt;code&gt;fork&lt;/code&gt; syscall is used which clones the calling process (including the code, the data, and call-stack), and then &lt;code&gt;exec&lt;/code&gt; is used to overwrite (&lt;em&gt;'overlay&lt;/em&gt;') the cloned process and its data with the supplied program name!&lt;/p&gt;

&lt;p&gt;It's not just one process that uses all the memory or CPU though; with multiprocessing, the system gives the CPU to processes that need it most. The &lt;strong&gt;scheduler&lt;/strong&gt; chooses which process is most appropriate to run, by selecting them out of the &lt;strong&gt;run queue&lt;/strong&gt;. It often uses a priority-based scheduling algorithm, but there are different types (e.g., round-robin, or first-in-first-out). The scheduler runs after processes are put onto the wait queue (e.g., whilst they are waiting for a system resource), or when a syscall is ending and the CPU is switching back to user mode.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;niceness&lt;/em&gt; of a process is a user-defined priority (ranging from -20 to 19 – highest to lowest) that can be given to processes using the &lt;code&gt;nice -n NICENESS_VALUE&lt;/code&gt; command, e.g. &lt;code&gt;nice -n -15 ./my-important-program&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Processes have different states: &lt;em&gt;running&lt;/em&gt; (the current process in the system, or ready to run); &lt;em&gt;waiting&lt;/em&gt; (waiting for an event/resource); &lt;em&gt;stopped&lt;/em&gt; (stopped due to a signal); &lt;em&gt;zombie&lt;/em&gt; (halted processes which for some reason still have a &lt;code&gt;task_struct&lt;/code&gt; entry).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Threads&lt;/strong&gt; are a single execution sequence within a process; a process can contain many threads. They share the memory of the process they belong to, which means inter-thread-communication is cheaper than inter-process-communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inter-Process Communication (IPC)
&lt;/h3&gt;

&lt;p&gt;IPC allows processes to communicate with each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signals&lt;/strong&gt; are the classic example of this, e.g. &lt;code&gt;SIGHUP&lt;/code&gt;, &lt;code&gt;SIGINT&lt;/code&gt;, &lt;code&gt;SIGKILL&lt;/code&gt;, etc. These are asynchronous events set to processes and can be generated by shells, keyboards, or even errors. Processes can choose how to deal with (or ignore) most of these, except for two: &lt;code&gt;SIGSTOP&lt;/code&gt; (halts a process until &lt;code&gt;SIGCONT&lt;/code&gt; resumes it) and &lt;code&gt;SIGKILL&lt;/code&gt; (exits a process entirely). Only the kernel and superusers can send signals to processes, or processes with the same GID/UID as others!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipes&lt;/strong&gt; are another method of IPC, allowing redirection between commands – they are one-way byte streams connecting the standard-out (stdout) of one process to the standard-in (stdin) of another. These are implemented using two files with the same temporary VFS inode, and are an abstraction as neither process is aware of the pipe!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sockets&lt;/strong&gt; are another method of IPC; these are pseudo-files that represent the network connection and can be read/written to using &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt;/&lt;code&gt;send&lt;/code&gt;/&lt;code&gt;recv&lt;/code&gt; syscalls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's a lot of information! I do hope this post helped you understand a tiny bit more about Linux (and Operating Systems in general), and how it works!&lt;/p&gt;

&lt;p&gt;If you're interested in learning more, I found &lt;a href="https://tldp.org/LDP/tlk/tlk.html" rel="noopener noreferrer"&gt;&lt;em&gt;The Linux Kernel&lt;/em&gt; by David A Rusling&lt;/a&gt; extremely useful, which goes into a lot more detail about the above topics, and much more.&lt;/p&gt;

&lt;p&gt;If you have any feedback, spotted any errors (or just want to chat!), please feel free to leave a comment below, or &lt;a href="https://sjain.dev" rel="noopener noreferrer"&gt;get in touch with me&lt;/a&gt; in any other way!&lt;/p&gt;

</description>
      <category>linux</category>
    </item>
    <item>
      <title>Single Sign On (SSO) with subdomains using Caddy v2</title>
      <dc:creator>Shubham</dc:creator>
      <pubDate>Sat, 29 May 2021 11:30:43 +0000</pubDate>
      <link>https://dev.to/shu8/single-sign-on-sso-with-subdomains-using-caddy-v2-l2i</link>
      <guid>https://dev.to/shu8/single-sign-on-sso-with-subdomains-using-caddy-v2-l2i</guid>
      <description>&lt;p&gt;&lt;strong&gt;[Update May 2022]&lt;/strong&gt;: The Caddy plugins used have been updated since I initially published this post. Please check out an updated version of this blog post on my site at: &lt;a href="https://blog.sjain.dev/caddy-sso/" rel="noopener noreferrer"&gt;https://blog.sjain.dev/caddy-sso/&lt;/a&gt; for the latest instructions!&lt;/p&gt;

&lt;p&gt;I've recently taken an interest in &lt;a href="https://github.com/awesome-selfhosted/awesome-selfhosted" rel="noopener noreferrer"&gt;self-hosting&lt;/a&gt; simple open source applications — to have fun, take control of my privacy, and learn more about Linux, Docker and DevOps!&lt;/p&gt;

&lt;p&gt;However, with this comes the need to add some form of authentication in front of all your services. For example, you probably don't want just anyone to be able to view all your RSS feeds, or use your markdown editor freely!&lt;/p&gt;

&lt;p&gt;The most basic form is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication" rel="noopener noreferrer"&gt;HTTP Basic Authentication&lt;/a&gt;, which is a pain as it must be configured and re-entered for each subdomain/service. You also need to type it out for each service, which can be challenging on mobile.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.cloudflare.com/en-gb/learning/access-management/what-is-sso/" rel="noopener noreferrer"&gt;Single Sign On (SSO)&lt;/a&gt; on the other hand allows you to authenticate with different services with a single login. For example, if you have a Google account, you can go to &lt;a href="http://youtube.com" rel="noopener noreferrer"&gt;youtube.com&lt;/a&gt;, &lt;a href="http://gmail.com" rel="noopener noreferrer"&gt;gmail.com&lt;/a&gt;, &lt;a href="http://drive.google.com" rel="noopener noreferrer"&gt;drive.google.com&lt;/a&gt;, etc., without having to enter your login details again for each site — and each site has the same login details, saving you precious time!&lt;/p&gt;

&lt;p&gt;There are already a few really useful guides and write-ups on using Caddy to set up an SSO system (e.g., see &lt;a href="https://joshstrange.com/securing-your-self-hosted-apps-with-single-signon/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://josheli.com/knob/2021/02/24/single-sign-on-in-caddy-server-using-only-the-caddyfile-and-basic-authentication/" rel="noopener noreferrer"&gt;here&lt;/a&gt;), but I wasn't able to immediately figure out how to get this working with the latest Caddy v2 and use it on subdomains. I still wanted to stick with Caddy despite this, as it is extremely easy to set-up and provides automatic HTTPS certificates out-of-the-box, which is really useful when getting started!&lt;/p&gt;

&lt;p&gt;This short blog post shows you how to configure a simple email/password SSO system with Caddy v2! By the end, it is as simple as adding &lt;em&gt;a single line&lt;/em&gt; to add SSO to your subdomain!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Download Caddy v2 with plugins
&lt;/h3&gt;

&lt;p&gt;You need the plugins &lt;a href="https://github.com/greenpau/caddy-auth-portal" rel="noopener noreferrer"&gt;caddy-auth-portal&lt;/a&gt; and &lt;a href="https://github.com/greenpau/caddy-auth-jwt" rel="noopener noreferrer"&gt;caddy-auth-jwt&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You can get Caddy with the plugins through a variety of options: manually building from source, downloading the pre-built version &lt;a href="https://caddyserver.com/download" rel="noopener noreferrer"&gt;from their download page&lt;/a&gt;, or (the easiest) using &lt;a href="https://caddyserver.com/download?package=github.com%2Fgreenpau%2Fcaddy-auth-portal&amp;amp;package=github.com%2Fgreenpau%2Fcaddy-auth-jwt" rel="noopener noreferrer"&gt;this direct link&lt;/a&gt; with the plugins pre-populated!&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Set up caddy-auth-jwt
&lt;/h3&gt;

&lt;p&gt;At the top of your &lt;code&gt;Caddyfile&lt;/code&gt;, add the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(sso) {
    jwt {
        trusted_tokens {
            status_secret {
                token_name access_token
                token_secret YOUR_SECRET_EHERE
            }
        }

        auth_url https://auth.mydomain.com
        allow roles user
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This creates a special '&lt;a href="https://caddyserver.com/docs/caddyfile/concepts#snippets" rel="noopener noreferrer"&gt;snippet&lt;/a&gt;' that can be reused and &lt;code&gt;import&lt;/code&gt;ed in other blocks by simply using &lt;code&gt;import sso&lt;/code&gt; (&lt;code&gt;sso&lt;/code&gt; can be called whatever you want, as long as you're consistent with the naming throughout your &lt;code&gt;Caddyfile&lt;/code&gt;!).&lt;/p&gt;

&lt;p&gt;Make sure you replace &lt;code&gt;YOUR_SECRET_HERE&lt;/code&gt; with a randomly generated secure secret, and update the &lt;code&gt;auth_url&lt;/code&gt; to whatever you want (e.g., for me it could be &lt;code&gt;https://auth.sjain.dev&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I'm not going to go over &lt;code&gt;roles&lt;/code&gt; in this post, but the &lt;a href="https://github.com/greenpau/caddy-auth-portal" rel="noopener noreferrer"&gt;official caddy-auth-portal examples&lt;/a&gt; show you exactly how to use it! For my case, I'm only using it to authenticate my own account.&lt;/p&gt;

&lt;p&gt;Note: you should &lt;a href="https://github.com/greenpau/caddy-auth-portal#jwt-tokens" rel="noopener noreferrer"&gt;read the docs&lt;/a&gt; to understand alternatives for managing your JWTs, which might suit your environment better. For example, you might choose to use environment variables, or a private key in a file.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Set up your Caddyfile's directive &lt;em&gt;order&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;In Caddy v2, there's a &lt;a href="https://caddyserver.com/docs/caddyfile/directives#directive-order" rel="noopener noreferrer"&gt;pre-set order of precedence for directives&lt;/a&gt;. See &lt;a href="https://github.com/caddyserver/caddy/issues/3438#issuecomment-633048762" rel="noopener noreferrer"&gt;this issue on GitHub&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;jwt&lt;/code&gt; and &lt;code&gt;auth_portal&lt;/code&gt; (the directives from the two plugins we installed) aren't in that pre-set; so we need to tell Caddy where they lie in the order of precedence.&lt;/p&gt;

&lt;p&gt;Just after your &lt;code&gt;sso&lt;/code&gt; snippet, add the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    order jwt before reverse_proxy
    order auth_portal before jwt
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  4. Configure your SSO!
&lt;/h3&gt;

&lt;p&gt;Now you can use the &lt;code&gt;auth_portal&lt;/code&gt; directive to configure your SSO setup:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auth.mydomain.com {
    auth_portal {
        path /
        cookie_domain mydomain.com
        backends {
            local_backend {
                method local
                path /etc/caddy/auth/local/users.json
                realm local
            }
        }
        jwt {
            token_name access_token
            token_secret YOUR_SECRET_HERE
        }
        ui {
            links {
                "RSS" https://rss.mydomain.com
                "Notes" https://notes.mydomain.com
            }
        }
        registration {
            dropbox /etc/caddy/auth/local/users.json
            code "YOUR_CODE_HERE"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Make sure you set the &lt;code&gt;auth.mydomain.com&lt;/code&gt; to be your actual domain (again, it doesn't have to be &lt;code&gt;auth.&lt;/code&gt; — it can be whatever you want as long as you're consistent). You'll also need to update your domain's DNS settings to account for this new subdomain.&lt;/p&gt;

&lt;p&gt;It's crucial to update the &lt;code&gt;cookie_domain&lt;/code&gt; — this is what makes it work for your subdomains!&lt;/p&gt;

&lt;p&gt;Update the &lt;code&gt;token_secret&lt;/code&gt; to be what you said in the &lt;code&gt;jwt&lt;/code&gt; directive.&lt;/p&gt;

&lt;p&gt;If you want the users to be stored elsewhere then you can update the &lt;code&gt;path&lt;/code&gt; directive of &lt;code&gt;local_backend&lt;/code&gt;. You'll also need to update this file to change user roles.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ui&lt;/code&gt; directive is optional: if enabled, when authenticated on &lt;code&gt;auth.mydomain.com&lt;/code&gt;, you will be provided with a very handy list of all your services:&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%2Fjw0cw5c4uz5sz71r6ks7.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%2Fjw0cw5c4uz5sz71r6ks7.png" alt="List of URLs" width="443" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;registration&lt;/code&gt; directive is also optional: if enabled, there will be a registration option on the login screen, and one of the registration form fields will be the &lt;code&gt;code&lt;/code&gt; as an extra step so only people you want (or who know the code) can register.&lt;/p&gt;

&lt;p&gt;For simplicity, you might enable registration temporarily to create your own account, and then disable it — depending on any risk to your existing services! Alternatively, you can copy/paste the &lt;code&gt;superuser&lt;/code&gt; in the JSON file you specified and update the duplicate to be your own details, e.g., change the password (using bcrypt), the roles, ID, username, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Use your SSO!
&lt;/h3&gt;

&lt;p&gt;That's all the config done! Now whenever you want to enable SSO for a subdomain/service, just &lt;code&gt;import sso&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There's a caveat though: &lt;em&gt;one&lt;/em&gt; of your subdomains/routes needs to be marked as &lt;code&gt;primary yes&lt;/code&gt; (for reasons explained &lt;a href="https://github.com/greenpau/caddy-auth-jwt#overview" rel="noopener noreferrer"&gt;here&lt;/a&gt;), but the &lt;code&gt;sso&lt;/code&gt; snippet we defined didn't have this. So, you'll need to copy and paste the config into one of your routes and add &lt;code&gt;primary yes&lt;/code&gt; before you can just use &lt;code&gt;import sso&lt;/code&gt; in the rest.&lt;/p&gt;

&lt;p&gt;For example, for the subdomain &lt;code&gt;mainservice.mydomain.com&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mainservice.mydomain.com {
    jwt {
        primary yes # XXX: this is the addition
        trusted_tokens {
            status_secret {
                token_name access_token
                token_secret YOUR_SECRET_EHERE
            }
        }
        auth_url https://auth.mydomain.com
        allow roles user
    }

    reverse_proxy http://localhost:1234
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And then for &lt;code&gt;rss.mydomain.com&lt;/code&gt; (and any other subdomains):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rss.mydomain.com {
    import sso
    reverse_proxy http://localhost:1234
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now whenever anyone navigates to &lt;code&gt;rss.mydomain.com&lt;/code&gt; or &lt;code&gt;mainservice.mydomain.com&lt;/code&gt;, if they are not logged in, they will be redirected to &lt;code&gt;auth.mydomain.com&lt;/code&gt; and will have to log in:&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%2Fj2j4jpfj8v4s19ycr4ww.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%2Fj2j4jpfj8v4s19ycr4ww.png" alt="Login screen" width="435" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're already logged in (your browser will have a cookie), then it will just let you in!&lt;/p&gt;

&lt;p&gt;The cookie will be shared across your subdomains, so you can freely switch between all your services without the pain of logging in every time!&lt;/p&gt;

&lt;p&gt;What if you want some paths to be public but not all? You could do this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service.mydomain.com {
    @allow path /public /anothersafepath
    handle @allow {
        reverse_proxy http://localhost:1234
    }

    import sso
    reverse_proxy http://localhost:1234
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Because of the *order*s we set up earlier, this simply works from top-to-bottom because &lt;code&gt;handle&lt;/code&gt; has higher precedence than &lt;code&gt;jwt&lt;/code&gt; (from the &lt;code&gt;import sso&lt;/code&gt;) and also &lt;code&gt;reverse_proxy&lt;/code&gt;. &lt;a href="https://caddyserver.com/docs/caddyfile/directives#directive-order" rel="noopener noreferrer"&gt;Here's the pre-defined order for reference&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;If you wanted to instead only keep some paths secret, you could change the second line to &lt;code&gt;@reject not path /secret /anothersecretpath&lt;/code&gt; (and change &lt;code&gt;@allow&lt;/code&gt; to &lt;code&gt;@reject&lt;/code&gt; in the third line!).&lt;/p&gt;

&lt;p&gt;I hope this post helps setting up your SSO with Caddy. I'd highly recommend trying it out if you find yourself always needing to authenticate with different services on your domain – and check out &lt;a href="https://github.com/greenpau/caddy-auth-portal" rel="noopener noreferrer"&gt;caddy-auth-portal's docs&lt;/a&gt; for even more advanced features!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: authentication is extremely important for a variety of services so please ensure your configuration works for you. In this blog I detail how you might use SSO but am in no way accountable for any privacy or confidentiality breaches as this is dependent on your configuration.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>tutorial</category>
      <category>caddyserver</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>My experience as an MLH Fellow of Class 0</title>
      <dc:creator>Shubham</dc:creator>
      <pubDate>Fri, 21 Aug 2020 11:53:25 +0000</pubDate>
      <link>https://dev.to/shu8/my-experience-as-an-mlh-fellow-of-class-0-973</link>
      <guid>https://dev.to/shu8/my-experience-as-an-mlh-fellow-of-class-0-973</guid>
      <description>&lt;p&gt;A few months ago, COVID-19 began to take a grip on life and the UK entered full lockdown. Just as I began to lose hope of finding a tech internship this Summer, I received DEV's weekly newsletter linking to &lt;a href="https://dev.to/devteam/dev-is-participating-in-the-mlh-fellowship-powered-by-github-5afk"&gt;a post announcing their involvement with the MLH Fellowship&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/devteam/dev-is-participating-in-the-mlh-fellowship-powered-by-github-5afk"&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%2Fi%2Fmen0dyyh7n0e58p89uyc.png" alt="DEV newsletter: " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It sparked my interest as I noticed that the Fellowship was all about open source code. Being a &lt;a href="https://github.com/soscripted/sox/" rel="noopener noreferrer"&gt;maintainer of another open source project&lt;/a&gt;, I was aware of the importance of open source contributions but never had the time or courage (after all, it is quite scary) to do it myself. This seemed like an ideal way to start!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I applied &lt;em&gt;immediately&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm incredibly grateful to have been selected amongst almost 20,000 global applications and to have had the opportunity to meet people from around the world whilst levelling up my technical skills!&lt;/p&gt;

&lt;p&gt;In this post, I go through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is the MLH Fellowship?&lt;/li&gt;
&lt;li&gt;The first week!&lt;/li&gt;
&lt;li&gt;The Hackathons 🥳&lt;/li&gt;
&lt;li&gt;The main Fellowship: Webaverse&lt;/li&gt;
&lt;li&gt;What did I learn?&lt;/li&gt;
&lt;li&gt;Shoutouts 😍&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ❓ What is the MLH Fellowship? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://fellowship.mlh.io/" rel="noopener noreferrer"&gt;MLH Fellowship&lt;/a&gt; is a 12-week internship for aspiring software engineers created because of the lack of job opportunities across the world due to the pandemic.&lt;/p&gt;

&lt;p&gt;The 3-stage application process was fairly simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Written application&lt;/strong&gt;: A form centered around your skills, interests and reasons for applying to be an MLH fellow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screening interview&lt;/strong&gt;: A 10 minute video call chatting about the information you provided earlier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical interview&lt;/strong&gt;: Another short video call walking through some open source code you've written in the past whilst screen sharing. You can choose how you want to go through your code, but don't worry if you can't go through all of it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're offered a place, congratulations! You are then placed into "Pods" of ~10 students and a full-time industry mentor to support you and help develop your skills (ranging from technical to entrepreneurial!) -- just one of the unique features of the Fellowship.&lt;/p&gt;

&lt;p&gt;I was in &lt;a href="https://github.com/orgs/MLH-Fellowship/teams/pod-0-2-1" rel="noopener noreferrer"&gt;Pod 0.2.1 -- the &lt;strong&gt;Distributed Dodos&lt;/strong&gt;&lt;/a&gt; where I came to know 10 awesome Fellows from around the world, each with a unique background that made the Fellowship an enriching experience. Our Pod focused on projects involving JavaScript but we weren't limited to this -- one of &lt;a href="https://github.com/webaverse/qrvr/pull/3" rel="noopener noreferrer"&gt;the issues I worked on&lt;/a&gt; in my project used C++!&lt;/p&gt;

&lt;p&gt;Despite it being virtual, you get plenty of opportunities to interact with your Pod and Mentor through daily meetings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three ~30 minute standups to update your Pod on your progress and get help with any blockers you might be facing.&lt;/li&gt;
&lt;li&gt;One ~60 minute retrospective, where you highlight your "red" (bad), "yellow" (not so well) and "green" (good) areas of the week. These give you that boost of encouragement, support and appreciation you might need as everyone is there to listen and help, without judgement or blame!&lt;/li&gt;
&lt;li&gt;One ~60 minute show-and-tell where either a Fellow from your Pod or your mentor showcases something they've created or are passionate about. I found it really interesting to learn about my Podmates' side projects, businesses and startups!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1️⃣ The first week &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A concern I had was how isolated I might feel working full-time and remotely for the first time, but the MLH team have worked tirelessly to plan a fun and engaging Fellowship!&lt;/p&gt;

&lt;p&gt;In the first few days, I was introduced to my Pod and Podmates through various Zoom meetings and was added to the Fellowship Discord server, which has helped make everyone feel at home throughout the program. The server had channels for a multitude of topics to encourage fruitful discussions and if one wasn't there, the MLH team would happily add it.&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%2Fi%2Fubb90upd30j88hjp9pe0.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%2Fi%2Fubb90upd30j88hjp9pe0.png" alt="The MLH Fellowship Discord Server" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We scheduled 1-on-1's with our Podmates after the first introductory Zoom meeting. These helped me feel much more comfortable about working in the Fellowship for 12 more weeks; I really recommend making use of 1-on-1's to get to know people remotely!&lt;/p&gt;

&lt;h2&gt;
  
  
  🥳 Hackathons (week 1 &amp;amp; 7) &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Personally, I &lt;em&gt;love&lt;/em&gt; hackathons -- and for a program organised &lt;em&gt;by MLH&lt;/em&gt;, the hackathons were no surprise.&lt;/p&gt;

&lt;p&gt;The Fellowship had &lt;strong&gt;two&lt;/strong&gt; hackathons: at the beginning (3 days) and the middle (5 days). Having attended just under 10 hackathons, these were the longest I've ever been to (most span just one weekend). I was amazed by how much more we accomplished with just a few more days, and this made them my favourite by far!&lt;/p&gt;

&lt;p&gt;Of course, the success (we won in both! 🎉) and enjoyment of these hackathons wouldn't have been possible without my amazing teammates: &lt;a href="https://ivov.dev/" rel="noopener noreferrer"&gt;Iván Ovejero&lt;/a&gt; and &lt;a href="https://github.com/kendevops" rel="noopener noreferrer"&gt;Kenneth Aladi&lt;/a&gt; -- the most passionate, talented, and dedicated hackathon partners I could have asked for 😍!&lt;/p&gt;

&lt;p&gt;During both hackathons, I learnt &lt;em&gt;so much&lt;/em&gt;. I was the main backend developer in my team, and I used AWS Amplify and AWS services (Lambdas, API Gateway, DynamoDB, CloudWatch, EC2) for the first time ever. Previously, AWS intimidated me as it seemed like a massive service that would be too difficult to jump into myself -- but knowing I had the support of my teammates and mentor, I was able to try it and came out much more confident! I also learnt more about React state management, how to make Discord bots, how to deploy to Heroku, how to use GitHub effectively, and so much more!&lt;/p&gt;

&lt;h3&gt;
  
  
  Orientation Hackathon (week 1): FellowBook (2nd place!)
&lt;/h3&gt;

&lt;p&gt;In the first hackathon, we came &lt;strong&gt;2nd place&lt;/strong&gt; out of over 30 global teams (~115 Fellows). We created &lt;a href="https://devpost.com/software/mlh-fellowbook" rel="noopener noreferrer"&gt;FellowBook&lt;/a&gt; -- a full blown picture-based web-app to find our Fellows and a fully-featured Discord Bot named &lt;em&gt;fellowbot&lt;/em&gt; who the Fellows came to know and love throughout the Fellowship:&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%2Fi%2Fhentl6cqy0n2yd3kcypf.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%2Fi%2Fhentl6cqy0n2yd3kcypf.png" alt="Fellowbot in action" width="370" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Halfway Hackathon (week 7): FellowHub (Winner!)
&lt;/h3&gt;

&lt;p&gt;My team and I didn't just stop after the first hackathon -- we kept tweaking and improving the fellowbot by adding features like randomised lists of fellows from each Pod to aid in standups.&lt;/p&gt;

&lt;p&gt;So naturally, for the second Hackathon we reunited and redesigned FellowBook &lt;em&gt;from scratch&lt;/em&gt; to create &lt;a href="https://devpost.com/software/fellowhub" rel="noopener noreferrer"&gt;FellowHub&lt;/a&gt;. We came &lt;strong&gt;1st place in the Getting Help category&lt;/strong&gt; out of over 25 global teams! We focused on making the Fellowship as easy as possible to navigate, aiding fellows with job searches, and boosting their social/developer presence:&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%2Fi%2Fwr684co6qg4jg6pfwtrl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwr684co6qg4jg6pfwtrl.gif" alt="Fellowhub homepage" width="760" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We even created individual portfolio pages for each Fellow to easily refer to their PRs, Issues and Standups from the Fellowship, and an Exchange Network to promote Fellows' open source projects:&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%2Fi%2Fw5jo0d5wo7g01uu8t38r.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%2Fi%2Fw5jo0d5wo7g01uu8t38r.png" alt="Fellowhub Portfolio page" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We used &lt;a href="http://primer.style/" rel="noopener noreferrer"&gt;GitHub's Primer Design System&lt;/a&gt; for the first time ever to create the website. I was astonished to see how much we accomplished in just a few days and how professional our site looked and behaved.&lt;/p&gt;

&lt;h2&gt;
  
  
  👨🏽‍💻 The main Fellowship: Webaverse &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;So what did I get up to in the main Fellowship?&lt;/p&gt;

&lt;p&gt;I was assigned to the &lt;a href="https://webaverse.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Webaverse&lt;/strong&gt; project&lt;/a&gt; (find it on GitHub &lt;a href="https://github.com/webaverse/" rel="noopener noreferrer"&gt;here&lt;/a&gt;) with 2 other Podmates.&lt;/p&gt;

&lt;p&gt;The Webaverse is a virtual network of apps that can run anywhere in VR, powered by IPFS, Ethereum and WebXR -- there is a great focus on making it an open, decentralised experience for anyone, and uses open standards. It also aims to give you full ownership of content (avatars, wearables, worlds, objects, etc.) you create.&lt;/p&gt;

&lt;p&gt;At first, it seemed quite daunting because I had zero experience in VR, WebXR, Service Workers, Ethereum, IPFS -- pretty much everything that this project seemed to be related to... 🙃&lt;/p&gt;

&lt;p&gt;But the maintainer, &lt;a href="https://twitter.com/webmixedreality/" rel="noopener noreferrer"&gt;Avaer Kazmer&lt;/a&gt; was incredibly patient and even did an informal 1.5 hour teaching stream (in VR 🤯) introducing my team and I to ThreeJS, WebXR and the Webaverse -- which is available &lt;a href="https://www.youtube.com/watch?v=YGsXRSEemRw" rel="noopener noreferrer"&gt;on YouTube&lt;/a&gt; for anyone to view now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=YGsXRSEemRw" rel="noopener noreferrer"&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%2Fi%2F25zzidbhnx6nkikz8ems.gif" alt="" width="480" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(yes, it was as cool as it looks! 😃)&lt;/p&gt;

&lt;p&gt;Most of my Summer was spent working on &lt;a href="https://github.com/webaverse/xrpackage" rel="noopener noreferrer"&gt;XRPackage&lt;/a&gt; -- the packaging system which lets you bundle many different types of content (e.g. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API" rel="noopener noreferrer"&gt;WebXR sites&lt;/a&gt;, &lt;a href="https://www.blender.org/" rel="noopener noreferrer"&gt;Blender 3D models&lt;/a&gt;, &lt;a href="https://vroid.com/en/studio/" rel="noopener noreferrer"&gt;VRoid Studio avatars&lt;/a&gt;, &lt;a href="https://www.khronos.org/gltf/" rel="noopener noreferrer"&gt;glTF models&lt;/a&gt;) into something that can run in VR and interact with the world seamlessly.&lt;/p&gt;

&lt;p&gt;The 'magic' of XRPackage is this: you can move content that was &lt;em&gt;never intended to be used in VR&lt;/em&gt; into VR, without much effort at all! I found this pretty amazing -- you can develop 3D content using tools and software you're already familiar with (like Blender), and then use the &lt;a href="https://xrpackage.org/inspect.html" rel="noopener noreferrer"&gt;web interface&lt;/a&gt; or &lt;a href="https://github.com/webaverse/xrpackage-cli" rel="noopener noreferrer"&gt;CLI tool&lt;/a&gt; to package it up into a single file (which you can share on the decentralised IPFS network, or through Ethereum) to run in VR!&lt;/p&gt;

&lt;p&gt;I'm really proud of what I've accomplished this Summer: &lt;a href="https://github.com/search?q=author%3Ashu8+is%3Apr+created%3A%3E2020-06-01+org%3Awebaverse&amp;amp;type=Issues" rel="noopener noreferrer"&gt;over &lt;strong&gt;50&lt;/strong&gt; pull requests&lt;/a&gt; ranging from &lt;a href="https://github.com/webaverse/xrpackage-site/pull/24" rel="noopener noreferrer"&gt;UI/UX tweaks&lt;/a&gt; and &lt;a href="https://github.com/webaverse/xrpackage/pull/111" rel="noopener noreferrer"&gt;fixing core packaging bugs&lt;/a&gt;, to &lt;a href="https://github.com/webaverse/xrpackage/pull/102" rel="noopener noreferrer"&gt;creating 2 test suites&lt;/a&gt; and &lt;a href="https://github.com/webaverse/docs/issues/5" rel="noopener noreferrer"&gt;over 15 PRs to write the majority of the Webaverse documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When we started working on the Webaverse, there was no formal documentation -- which worried us all! However, I'm thrilled to have been the core contributor to the &lt;a href="https://docs.webaverse.com/" rel="noopener noreferrer"&gt;new documentation site&lt;/a&gt; which involved thorough digging into and experimenting with the codebase, research into good documentation practices, and improving my technical writing skills to produce concise, user-friendly documentation that anybody can now use to get started with the Webaverse:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.webaverse.com/" rel="noopener noreferrer"&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%2Fi%2Frr67ku84w8ypvcry3biu.png" alt="Webaverse Documentation" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I setup 2 test frameworks from scratch, for the &lt;a href="https://github.com/webaverse/xrpackage/pull/101" rel="noopener noreferrer"&gt;core XRPackage code&lt;/a&gt; and &lt;a href="https://github.com/webaverse/xrpackage-cli/pull/30" rel="noopener noreferrer"&gt;CLI tool&lt;/a&gt; using &lt;a href="https://github.com/avajs/ava" rel="noopener noreferrer"&gt;AVA&lt;/a&gt; and &lt;a href="https://github.com/puppeteer/puppeteer/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt;, and used &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; for the first time to setup a CI environment. I also wrote over 15 unit tests (and as a result, discovered 3 bugs/improvements in the core code which I then fixed):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/webaverse/xrpackage/actions?query=workflow%3A%22XRPackage+AVA+test+runner+CI%22" rel="noopener noreferrer"&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%2Fi%2Faxk2cx8q6khhe8a0s9mw.png" alt="CI test runner" width="685" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We even wrote (and open-sourced) &lt;a href="https://github.com/MLH-Fellowship/webaverse-bot" rel="noopener noreferrer"&gt;a Discord bot&lt;/a&gt; that allows you to interact with worlds all from your keyboard! I wrote &lt;strong&gt;half of the bot's commands&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 What I've learnt (and why you should consider applying to be an MLH Fellow!) &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The most important lesson I learnt from the Fellowship is how to work remotely with people. It's not easy! I was used to working in person at my last job where I could chat to my team about a problem by pointing at the screen with my &lt;em&gt;hands&lt;/em&gt; instead of a mouse!&lt;/p&gt;

&lt;p&gt;Working remotely is one thing, but working remotely &lt;em&gt;and&lt;/em&gt; asynchronously -- with people across the world -- is another thing entirely; when one of us was working, the other would be asleep in another part of the world!&lt;/p&gt;

&lt;p&gt;During the Hackathons, we had a 4-hour time difference so we managed by working together for half the day and syncing up around lunch-time/morning for the other person.&lt;/p&gt;

&lt;p&gt;However during my main work, we had a &lt;strong&gt;time difference of around 8 hours&lt;/strong&gt; -- that's the entire working day! Working synchronously just wasn't possible and I found it very difficult to adjust in the first week. Here are some tips from my experience with dealing with this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Be willing to compromise&lt;/strong&gt;. Your team should find at least one time in the day (or week) where you can sync up and talk. If that means coming back online later in the evening for half an hour, or waking up a bit earlier every now and again, it would go a long way for the success of your project and team!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embrace working asynchronously&lt;/strong&gt;. You can still be an effective team even if you don't all work at the exact same time! My team started meeting every Monday to plan out any issues we wanted to finish during the week and discuss any blockers. We could then work independently and quickly pick up a new issue from the list if needed. We also managed to 'hand over' work -- if my teammate was 8 hours behind me working on a tough issue, then when I came online I could take over and provide a fresh perspective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communicate&lt;/strong&gt;. Communication really is key, in just about everything. Updating each other with your progress throughout your day helps keep everyone on the same page, even if it's only read by others a few hours later. I found this quite weird at first, because I was just typing into an empty Discord channel talking to myself throughout the day -- but it was definitely worth it for our team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of learning how to work &lt;em&gt;async remote&lt;/em&gt;, I gained a lot of technical skills and knowledge: WebXR, Ethereum, the blockchain, IPFS, VR, Service Workers, modern JavaScript debugging techniques, unit testing code effectively, setting up CI test runners, testing a CLI, writing Discord bots, and &lt;em&gt;lots&lt;/em&gt; more!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're looking to get into open source software, I really do suggest applying &lt;a href="https://fellowship.mlh.io/" rel="noopener noreferrer"&gt;for the next batch(es)&lt;/a&gt; of the MLH Fellowship now!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🙏🏽 Shoutouts &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I'm extremely grateful for my mentor, &lt;a href="https://jnnngs.com/" rel="noopener noreferrer"&gt;Ian Jennings&lt;/a&gt; for his invaluable guidance and passion for making the Fellowship as enriching as possible. In particular, his digressions in standups and advice on getting over the initial troubles with timezones have been awesome. Check out &lt;a href="https://dev.to/paircast/transform-coding-screencasts-into-markdown-for-dev-to-and-github-4k3f"&gt;his recent post&lt;/a&gt; about transforming code screencasts into markdown tutorials with his latest product: &lt;a href="https://paircast.io/" rel="noopener noreferrer"&gt;Paircast&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I'm also grateful for &lt;a href="https://twitter.com/webmixedreality/" rel="noopener noreferrer"&gt;Avaer Kazmer&lt;/a&gt; for the guidance, patience, and &lt;em&gt;so many&lt;/em&gt; code reviews throughout the Fellowship -- I've learnt a ton 🙏🏽! If you're interested, the Webaverse community is extremely friendly and would love contributions! Join the &lt;a href="https://t.co/cOtDuluGcc" rel="noopener noreferrer"&gt;Webaverse Discord here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have any questions about the Fellowship or anything else, I'd be more than happy to answer them in the comments, or you can find more ways to reach me &lt;a href="https://sjain.dev" rel="noopener noreferrer"&gt;on my website&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>mlhgrad</category>
      <category>career</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
