<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Emanuele Papa RSS Feed]]></title><description><![CDATA[Hi, I am Emanuele Papa, an Android developer who likes to explore other worlds. See what I do!]]></description><link>https://www.emanuelepapa.dev</link><generator>GatsbyJS</generator><lastBuildDate>Thu, 08 Jan 2026 10:01:54 GMT</lastBuildDate><item><title><![CDATA[Test GitHub Actions workflows on a branch before merging]]></title><description><![CDATA[If you’re developing a new GitHub Actions workflow, you may want to test it on a feature branch before merging it into your default branch…]]></description><link>https://www.emanuelepapa.dev/test-github-actions-workflows-on-a-branch-before-merging/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/test-github-actions-workflows-on-a-branch-before-merging/</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Mon, 29 Dec 2025 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/48306e9a389a1fe9c9b4dfed2c636531/architecture-5299050_1920.jpg" length="838511" type="image/jpg"/><content:encoded>&lt;p&gt;If you’re developing a new GitHub Actions workflow, you may want to test it on a feature branch before merging it into your default branch. Unfortunately, GitHub Actions has a limitation: workflows that only exist on a branch and use &lt;code&gt;workflow_dispatch&lt;/code&gt; cannot be triggered until GitHub has “seen” them at least once.&lt;/p&gt;&lt;p&gt;In this guide, we’ll walk through a simple and reliable workaround to test GitHub Actions workflows on a branch using a temporary &lt;code&gt;push&lt;/code&gt; trigger and the GitHub CLI.&lt;/p&gt;&lt;h2 id=&quot;-prerequisites&quot;&gt;📝 Prerequisites&lt;/h2&gt;&lt;p&gt;Before starting, make sure you have:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A GitHub repository with Actions enabled&lt;/li&gt;&lt;li&gt;A feature branch where you’re developing the workflow&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cli.github.com/&quot;&gt;GitHub CLI (&lt;code&gt;gh&lt;/code&gt;)&lt;/a&gt; installed and authenticated&lt;/li&gt;&lt;li&gt;Basic familiarity with GitHub Actions YAML syntax&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;️-step-by-step-testing-a-github-actions-workflow-on-a-branch&quot;&gt;🛠️ Step-by-step: Testing a GitHub Actions workflow on a branch&lt;/h2&gt;&lt;h3 id=&quot;1-create-a-skeleton-workflow-triggered-by-push&quot;&gt;1. Create a skeleton workflow triggered by &lt;code&gt;push&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;On your feature branch, create a new workflow file under &lt;code&gt;.github/workflows/&lt;/code&gt;, starting with a minimal trigger using &lt;code&gt;push&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;on:
  push:
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At this stage, the job can be incomplete or just a placeholder. The important part is that the workflow runs at least once.
Push the branch to GitHub so the workflow is executed.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;💡 You can cancel the job immediately if you don’t want it to finish.&lt;/p&gt;&lt;/blockquote&gt;&lt;h3 id=&quot;2-let-github-register-the-workflow&quot;&gt;2. Let GitHub register the workflow&lt;/h3&gt;&lt;p&gt;Once the workflow has run a single time, GitHub internally registers it.
This is a key step: without it, GitHub won’t allow manual triggers from branches.
After the first run, you no longer need the push trigger.&lt;/p&gt;&lt;h3 id=&quot;3-replace-push-with-workflow_dispatch&quot;&gt;3. Replace &lt;code&gt;push&lt;/code&gt; with &lt;code&gt;workflow_dispatch&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Now update the workflow to use &lt;code&gt;workflow_dispatch&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;on:
  workflow_dispatch:
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Push the updated workflow to the same branch.
Even though the workflow no longer triggers automatically, GitHub still recognizes it.&lt;/p&gt;&lt;h3 id=&quot;4-trigger-the-workflow-using-github-cli&quot;&gt;4. Trigger the workflow using GitHub CLI&lt;/h3&gt;&lt;p&gt;You can now manually run the workflow directly from your branch using the GitHub CLI:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gh workflow run &amp;lt;workflow-file-name&amp;gt;.yaml -r &amp;lt;branch-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This allows you to:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Test the workflow repeatedly&lt;/li&gt;&lt;li&gt;Iterate quickly without touching the default branch&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;5-continue-developing-and-testing&quot;&gt;5. Continue developing and testing&lt;/h3&gt;&lt;p&gt;As long as the workflow file remains in the branch, you can keep triggering it via &lt;code&gt;gh&lt;/code&gt; until it’s fully tested and ready to be merged.
No additional &lt;code&gt;push&lt;/code&gt; runs are required.&lt;/p&gt;&lt;h2 id=&quot;-conclusion&quot;&gt;✅ Conclusion&lt;/h2&gt;&lt;p&gt;Testing GitHub Actions workflows on feature branches can be tricky due to current platform limitations. By temporarily enabling a &lt;code&gt;push&lt;/code&gt; trigger, letting the workflow run once, and then switching to &lt;code&gt;workflow_dispatch&lt;/code&gt;, you can safely and effectively test your workflow without polluting your default branch.
Until GitHub provides better native support for branch-only manual workflows, this workaround is one of the cleanest and most practical solutions available.  &lt;/p&gt;&lt;p&gt;You can read more in &lt;a href=&quot;https://github.com/orgs/community/discussions/25746&quot;&gt;this GitHub discussion&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Happy automating 🚀&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How to change Proxmox IP address when moving to a different network]]></title><description><![CDATA[If you've migrated your Proxmox server to a different network, you might find yourself unable to access the web interface or SSH due to an…]]></description><link>https://www.emanuelepapa.dev/how-to-change-proxmox-ip-address-when-moving-to-a-different-network/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/how-to-change-proxmox-ip-address-when-moving-to-a-different-network/</guid><category><![CDATA[Smart Home]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Wed, 11 Jun 2025 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/427fed7ae9da61c0d9a7ab5982c4d40e/ai-generated-8427261_1920.jpg" length="827896" type="image/jpg"/><content:encoded>&lt;p&gt;If you’ve migrated your Proxmox server to a different network, you might find yourself unable to access the web interface or SSH due to an outdated IP configuration. In this guide, we’ll see the necessary steps to update the IP address and restore connectivity to your Proxmox VE node.&lt;/p&gt;&lt;h2 id=&quot;-prerequisites&quot;&gt;📝 Prerequisites&lt;/h2&gt;&lt;p&gt;Before starting, ensure you have:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Physical or console access to your Proxmox server&lt;/li&gt;&lt;li&gt;Root privileges&lt;/li&gt;&lt;li&gt;Knowledge of your new network configuration (IP address, subnet mask, gateway, DNS servers)&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;️-step-by-step-changing-proxmox-ip-address&quot;&gt;🛠️ Step-by-Step: Changing Proxmox IP address&lt;/h2&gt;&lt;h3 id=&quot;1-access-the-server-via-console&quot;&gt;1. Access the server via console&lt;/h3&gt;&lt;p&gt;If the old IP no longer works due to the network change, you’ll need to access your server physically or through a remote management console provided by your hosting provider.&lt;/p&gt;&lt;h3 id=&quot;2-update-the-network-configuration-file&quot;&gt;2. Update the Network Configuration File&lt;/h3&gt;&lt;p&gt;Proxmox stores its static network settings in the &lt;code&gt;/etc/network/interfaces&lt;/code&gt; file. Open it with your preferred text editor like:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nano /etc/network/interfaces
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Look for a section similar to this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;auto vmbr0
iface vmbr0 inet static
    address 192.168.1.100/24
    gateway 192.168.1.1
    bridge-ports eth0
    bridge-stp off
    bridge-fd 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Update the &lt;code&gt;address&lt;/code&gt; and &lt;code&gt;gateway&lt;/code&gt; values to match the new network configuration. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;address 192.168.100.50/24
gateway 192.168.100.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Save and exit.&lt;/p&gt;&lt;h3 id=&quot;3-update-etchosts&quot;&gt;3. Update &lt;code&gt;/etc/hosts&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;To avoid hostname resolution issues, update your &lt;code&gt;/etc/hosts&lt;/code&gt; file:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nano /etc/hosts
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Find and modify the line referencing the old IP:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;192.168.1.100 proxmox.yourdomain.local pve
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Change it to the new IP:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;192.168.100.50 proxmox.yourdomain.local pve
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;4-check-dns-settings&quot;&gt;4. Check DNS Settings&lt;/h3&gt;&lt;p&gt;If you’re using custom DNS servers, make sure they are updated:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nano /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Option 1: If using public DNS servers
nameserver 1.1.1.1

# Option 2: If your DNS servers are on your gateway
nameserver 192.168.100.1
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;5-restart-networking&quot;&gt;5. Restart Networking&lt;/h3&gt;&lt;p&gt;You can either restart the networking service or reboot the entire system:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Option 1: Restart networking
systemctl restart networking

# Option 2: Reboot
reboot
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;6-verify-the-new-configuration&quot;&gt;6. Verify the new configuration&lt;/h3&gt;&lt;p&gt;Now, try accessing the Proxmox web interface using the new IP at &lt;code&gt;https://192.168.100.50:8006&lt;/code&gt;&lt;/p&gt;&lt;h2 id=&quot;-conclusion&quot;&gt;✅ Conclusion&lt;/h2&gt;&lt;p&gt;Changing the IP address in Proxmox after moving to a different network is straightforward once you know where the settings live. Just be sure to update &lt;code&gt;/etc/network/interfaces&lt;/code&gt;, &lt;code&gt;/etc/hosts&lt;/code&gt;, &lt;code&gt;/etc/resolv.conf&lt;/code&gt; and restart networking.&lt;br/&gt;
With these steps, your node should be up and reachable on the new network.
Bear in mind, if your Proxmox server is part of a cluster, you might need to update additional files not covered in this blog post.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Block ads using DNS over TLS with AdGuard Home, Let's Encrypt, and Cloudflare for Android Devices]]></title><description><![CDATA[DNS over TLS (DoT) is a security protocol that encrypts DNS queries, providing enhanced privacy and security. This guide will walk you…]]></description><link>https://www.emanuelepapa.dev/block-ads-using-dns-over-tls-with-adguard-home-lets-encrypt-and-cloudflare-for-android-devices/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/block-ads-using-dns-over-tls-with-adguard-home-lets-encrypt-and-cloudflare-for-android-devices/</guid><category><![CDATA[Smart Home]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Tue, 22 Oct 2024 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/5d4728401b73f383c9ebd54892c024b0/kaffeebart-KrPulSdUetk-unsplash.jpg" length="305249" type="image/jpg"/><content:encoded>&lt;p&gt;DNS over TLS (DoT) is a security protocol that encrypts DNS queries, providing enhanced privacy and security. This guide will walk you through setting up AdGuard Home to serve DNS over TLS using your custom domain, secured with a Let’s Encrypt certificate. We’ll use Cloudflare for DNS management and integrate Home Assistant for automation. By the end, your Android devices will be able to use secure DNS queries to your AdGuard Home server. We will be focusing on Android devices but iOS devices can use it as well!&lt;/p&gt;&lt;h1 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h1&gt;&lt;p&gt;Before starting, ensure you have:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A registered domain name.&lt;/li&gt;&lt;li&gt;An active Cloudflare account managing your domain.&lt;/li&gt;&lt;li&gt;A running instance of Home Assistant.&lt;/li&gt;&lt;li&gt;AdGuard Home installed and running.&lt;/li&gt;&lt;/ul&gt;&lt;h1 id=&quot;step-1-create-dns-records-in-cloudflare&quot;&gt;Step 1: Create DNS Records in Cloudflare&lt;/h1&gt;&lt;p&gt;To enable DNS over TLS, you’ll need to set up the necessary DNS records in Cloudflare.&lt;/p&gt;&lt;h2 id=&quot;create-an-a-record&quot;&gt;Create an A Record:&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Log in to your Cloudflare dashboard.&lt;/li&gt;&lt;li&gt;Navigate to the DNS settings of your domain.&lt;/li&gt;&lt;li&gt;Create a new &lt;code&gt;A&lt;/code&gt; record with the following details:&lt;ul&gt;&lt;li&gt;Name: &lt;code&gt;dns&lt;/code&gt;&lt;/li&gt;&lt;li&gt;IPv4 Address: &lt;code&gt;Your server&amp;#x27;s public IP address&lt;/code&gt; (this will be updated automatically by the HomeAssistant Cloudflare integration, don’t worry!)&lt;/li&gt;&lt;li&gt;TTL: &lt;code&gt;Auto&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Proxy status: &lt;code&gt;DNS only (unproxied)&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;create-a-cname-record&quot;&gt;Create a CNAME Record:&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Still in DNS settings, create a &lt;code&gt;CNAME&lt;/code&gt; record:&lt;ul&gt;&lt;li&gt;Name: &lt;code&gt;*.dns&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Target: &lt;code&gt;dns.yourdomain.com&lt;/code&gt;&lt;/li&gt;&lt;li&gt;TTL: &lt;code&gt;Auto&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Proxy status: &lt;code&gt;DNS only (unproxied)&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These records ensure that both &lt;code&gt;dns.yourdomain.com&lt;/code&gt; and any subdomains will direct traffic to your AdGuard Home instance.
This is useful to be able to create &lt;code&gt;clientname.dns.yourdomain.com&lt;/code&gt; to identify different clients by different hostnames.&lt;/p&gt;&lt;h1 id=&quot;step-2-configure-cloudflare-integration-in-home-assistant&quot;&gt;Step 2: Configure Cloudflare Integration in Home Assistant&lt;/h1&gt;&lt;p&gt;Home Assistant can automate updating your Cloudflare DNS records if your server’s IP address changes.&lt;/p&gt;&lt;h2 id=&quot;create-api-tokens-in-cloudflare&quot;&gt;Create API Tokens in Cloudflare:&lt;/h2&gt;&lt;p&gt;To interact with Cloudflare API we need two different tokens:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;used in the Cloudflare HomeAssistant Integration to update the DNS record with our IP&lt;/li&gt;&lt;li&gt;used in the Let’s Encrypt HomeAssistant add-on to automate DNS challenges for domain validation&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;To generate those:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Go to &lt;em&gt;My Profile&lt;/em&gt; &amp;gt; &lt;em&gt;API Tokens&lt;/em&gt; in your Cloudflare account.&lt;/li&gt;&lt;li&gt;Click &lt;em&gt;Create Token&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;Use the &lt;em&gt;Create Custom Token&lt;/em&gt; button and add the following permissions for your domain Zone:&lt;ul&gt;&lt;li&gt;&lt;code&gt;Zone:Zone:Read&lt;/code&gt; and &lt;code&gt;Zone:DNS:Edit&lt;/code&gt; for Token 1&lt;/li&gt;&lt;li&gt;&lt;code&gt;Zone:DNS:Edit&lt;/code&gt; for Token 2&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Copy the generated token.&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;install-and-configure-the-cloudflare-integration&quot;&gt;Install and configure the &lt;a href=&quot;https://www.home-assistant.io/integrations/cloudflare/&quot;&gt;Cloudflare Integration&lt;/a&gt;:&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;In Home Assistant, go to &lt;em&gt;Settings&lt;/em&gt; &amp;gt; &lt;em&gt;Integrations&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;Search for &lt;em&gt;Cloudflare&lt;/em&gt; and install the integration.&lt;/li&gt;&lt;li&gt;Set it to update the &lt;code&gt;dns.yourdomain.com&lt;/code&gt; A record whenever your IP changes.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Note: it seems that it doesn’t run automatically as soon as it’s setup, so either you enter your current IP address in previous step, or you run the integration to update it for you using the service &lt;code&gt;cloudflare.update_records&lt;/code&gt;.
After that, it will run automatically every hour to update your IP.&lt;/p&gt;&lt;h1 id=&quot;step-3-obtain-and-configure-lets-encrypt-certificates&quot;&gt;Step 3: Obtain and Configure Let’s Encrypt Certificates&lt;/h1&gt;&lt;p&gt;Next, secure your domain with a Let’s Encrypt SSL certificate to enable DNS over TLS.&lt;/p&gt;&lt;h2 id=&quot;install-the-lets-encrypt-add-on-in-home-assistant&quot;&gt;Install the Let’s Encrypt Add-on in Home Assistant:&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;In Home Assistant, navigate to &lt;em&gt;Supervisor&lt;/em&gt; &amp;gt; &lt;em&gt;Add-on Store&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;Find and install the &lt;em&gt;Let’s Encrypt&lt;/em&gt; add-on.&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;configure-the-lets-encrypt-add-on&quot;&gt;Configure the Let’s Encrypt Add-on:&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;Set the domains to &lt;code&gt;dns.yourdomain.com&lt;/code&gt; and &lt;code&gt;*.dns.yourdomain.com&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Set the challenge to &lt;code&gt;dns&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Enter your Cloudflare API token to automate DNS challenges.&lt;/li&gt;&lt;li&gt;Start the add-on to generate your certificates.&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;set-up-automatic-certificate-renewal&quot;&gt;Set Up Automatic Certificate Renewal:&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;In Home Assistant, navigate to &lt;em&gt;Settings&lt;/em&gt; &amp;gt; &lt;em&gt;Automations and Scenes&lt;/em&gt; &amp;gt; &lt;em&gt;Automations&lt;/em&gt;&lt;/li&gt;&lt;li&gt;Click on &lt;em&gt;Create Automation&lt;/em&gt;&lt;/li&gt;&lt;li&gt;Use the following YAML and save the automation&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code&gt;alias: Start Let&amp;#x27;s Encrypt Add-on at Midnight
description: Start the Let&amp;#x27;s Encrypt add-on every day at midnight
mode: single
triggers:
  - at: &amp;quot;00:00:00&amp;quot;
    trigger: time
actions:
  - data:
      addon: core_letsencrypt
    action: hassio.addon_start
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;step-4-configure-adguard-home-for-dns-over-tls&quot;&gt;Step 4: Configure AdGuard Home for DNS over TLS&lt;/h1&gt;&lt;p&gt;With the SSL certificate ready, you can now configure AdGuard Home to support DNS over TLS.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Go to &lt;em&gt;AdGuard Home&lt;/em&gt;’s web interface.&lt;/li&gt;&lt;li&gt;Navigate to &lt;em&gt;Settings&lt;/em&gt; &amp;gt; &lt;em&gt;Encryption&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;Set the &lt;em&gt;Server Name&lt;/em&gt; to &lt;code&gt;dns.yourdomain.com&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Use default certificate path as we didn’t change it in Let’s Encrypt add-on configuration&lt;/li&gt;&lt;li&gt;Ensure certificate chain is validated (green lights)&lt;/li&gt;&lt;li&gt;Save and Apply Settings&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Your AdGuard Home server is now configured to serve DNS over TLS.&lt;/p&gt;&lt;h1 id=&quot;step-5-configure-android-devices-for-dns-over-tls&quot;&gt;Step 5: Configure Android Devices for DNS over TLS&lt;/h1&gt;&lt;p&gt;Different Android devices might have slightly different steps to setup DNS over TLS, but generally to use your new DNS over TLS setup on Android devices follow these steps:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Open &lt;em&gt;Settings&lt;/em&gt; &amp;gt; &lt;em&gt;Network &amp;amp; Internet&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;Tap &lt;em&gt;Private DNS&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;Choose &lt;em&gt;Private DNS provider hostname&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;Enter &lt;code&gt;dns.yourdomain.com&lt;/code&gt; or &lt;code&gt;clientname.dns.yourdomain.com&lt;/code&gt; (if you want to use client names, go to &lt;em&gt;Settings&lt;/em&gt; &amp;gt; &lt;em&gt;Client Settings&lt;/em&gt; on AdGuard and add needed clients)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Your Android device will now use DNS over TLS with your AdGuard Home server, ensuring encrypted DNS queries.
If your device says that your DNS over TLS server doesn’t exist, please check everything is configured above, or wait a bit of time to allow the DNS system to propate your configuration.&lt;/p&gt;&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;&lt;p&gt;By following these steps, you’ve successfully configured AdGuard Home to provide DNS over TLS, secured with a Let’s Encrypt certificate and managed through Cloudflare. Your Android devices are now equipped to handle encrypted DNS queries, enhancing your privacy and security. This setup not only secures your DNS traffic but also allows for easy management and automation using Home Assistant.&lt;/p&gt;&lt;p&gt;Feel free to adjust the steps to fit your environment, and enjoy a more secure internet experience on your Android devices.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Synchronize AdGuard Home instances with Docker and Proxmox]]></title><description><![CDATA[In today's digital age, ensuring privacy and security on our networks has become increasingly important. AdGuard Home, a powerful network…]]></description><link>https://www.emanuelepapa.dev/synchronize-adguard-home-instances-with-docker-and-proxmox/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/synchronize-adguard-home-instances-with-docker-and-proxmox/</guid><category><![CDATA[Smart Home]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Tue, 23 Apr 2024 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/936bccb66a7d4c757413c830f8bdcbc6/pexels-photo-277458.jpeg" length="104033" type="image/jpeg"/><content:encoded>&lt;p&gt;In today’s digital age, ensuring privacy and security on our networks has become increasingly important. AdGuard Home, a powerful network-wide ad blocking and privacy protection tool, has gained popularity for its effectiveness in achieving these goals. However, managing multiple instances of AdGuard Home across different devices in a homelab setup can be cumbersome. In this article, we’ll explore how I tackled this challenge by using the AdGuard Home Sync tool, simplifying the synchronization process and ensuring consistent filtering and configuration across all my devices.&lt;/p&gt;&lt;h1 id=&quot;what-is-adguard-home-sync&quot;&gt;What is AdGuard Home Sync&lt;/h1&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/bakito/adguardhome-sync&quot;&gt;adguardhome-sync&lt;/a&gt; is a handy tool available on GitHub, designed to streamline the process of synchronizing configurations, whitelists, and blocklists across multiple AdGuard Home instances. By automating this synchronization, users can maintain uniform settings and preferences across their entire network effortlessly.&lt;/p&gt;&lt;h1 id=&quot;installation-steps&quot;&gt;Installation steps&lt;/h1&gt;&lt;p&gt;Here, I’m assuming you already have setup two (or more) instances of AdGuard Home.
You can follow one of the option provided in the &lt;a href=&quot;https://github.com/bakito/adguardhome-sync&quot;&gt;adguardhome-sync repository&lt;/a&gt; to setup AdGuard Home sync, but here we will setup it using docker in a proxmox environment, while having 1 AdGuard Home instance hosted by HomeAssistant, and another 1 hosted using docker.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Ensure both your AdGuard Home instances are up and running&lt;/li&gt;&lt;li&gt;Ensure you have a Proxmox home server set up and running&lt;/li&gt;&lt;li&gt;Use the Proxmox main shell to create an Alpine Docker LXC using &lt;a href=&quot;https://github.com/tteck/Proxmox/raw/main/ct/alpine-docker.sh&quot;&gt;this awesome script&lt;/a&gt; from &lt;a href=&quot;https://tteck.github.io/Proxmox/&quot;&gt;tteck&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;In the shell of the newly created container, move to &lt;code&gt;/docker/adguard-sync&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Now create a file &lt;code&gt;adguardhome-sync.yaml&lt;/code&gt; and paste the following content, adjusting it where needed:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;cron: &amp;#x27;*/10 * * * *&amp;#x27;
runOnStart: true
continueOnError: true
origin:
  url: &amp;#x27;{ORIGIN_ADGUARD_HOME_INSTANCE_URL}&amp;#x27;
  insecureSkipVerify: true
  username: {ORIGIN_ADGUARD_HOME_INSTANCE_USERNAME}
  password: {ORIGIN_ADGUARD_HOME_INSTANCE_PASSWORD}
replicas:
  - url: &amp;#x27;{REPLICA_ADGUARD_HOME_INSTANCE_URL}&amp;#x27;
    username: {REPLICA_ADGUARD_HOME_INSTANCE_USERNAME}
    password: {REPLICA_ADGUARD_HOME_INSTANCE_PASSWORD}
api:
  port: 8080
  username: {ADGUARD_HOME_SYNC_CONTROL_PANEL_USERNAME}
  password: {ADGUARD_HOME_SYNC_CONTROL_PANEL_PASSWORD}
  darkMode: true
features:
  generalSettings: true
  queryLogConfig: true
  statsConfig: true
  clientSettings: true
  services: true
  filters: true
  dhcp:
    serverConfig: true
    staticLeases: true
  dns:
    serverConfig: true
    accessLists: true
    rewrites: true
&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;3&quot;&gt;&lt;li&gt;Now, run the following Docker command:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d \
  -p 80:8080 \
  -v /docker/adguard-sync/adguardhome-sync.yaml:/config/adguardhome-sync.yaml \
  --restart unless-stopped \
  --name=adguardhome-sync \
  ghcr.io/bakito/adguardhome-sync:latest
&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;4&quot;&gt;&lt;li&gt;Once the container is up and running, your original AdGuard Home configuration will be replicated onto your replica instance every 10 minutes! You can also access the AdGuard Home sync web interface by navigating to &lt;code&gt;http://&amp;lt;your-container-ip&amp;gt;&lt;/code&gt; in your web browser.&lt;/li&gt;&lt;li&gt;You are all set, but keep reading for more information!&lt;/li&gt;&lt;/ol&gt;&lt;h2 id=&quot;adguard-home-sync-configuration&quot;&gt;AdGuard Home sync configuration&lt;/h2&gt;&lt;p&gt;Most of the settings in the yaml files are self-explanatory: what you should now is that you can have more than 1 replica, and that you can disable the web interface of AdGuard Home sync if you don’t want it.
I encourage you to read their readme to find all the info you might need.&lt;/p&gt;&lt;h2 id=&quot;how-to-get-usernamepassword-of-homeassistant-adguard-home&quot;&gt;How to get username/password of HomeAssistant AdGuard Home&lt;/h2&gt;&lt;p&gt;To enable access to the HomeAssistant AdGuard Home instance you have to do several things:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Turn on its web interface in the Network section of the Add-On configuration&lt;/li&gt;&lt;li&gt;To improve the security of your architecture, create a new HomeAssistant user that can access HomeAssistant only from the local network. This will be the one you will use to setup &lt;code&gt;{ORIGIN_ADGUARD_HOME_INSTANCE_USERNAME}&lt;/code&gt; in this case&lt;/li&gt;&lt;li&gt;The URL for &lt;code&gt;{ORIGIN_ADGUARD_HOME_INSTANCE_URL}&lt;/code&gt; is the local address of your HomeAssistant instance&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Thanks to &lt;a href=&quot;https://brunty.me/post/replicate-adguard-home-settings-into-home-assistant-adguard-home-addon/&quot;&gt;brunty.me&lt;/a&gt; for providing this insights!&lt;/p&gt;&lt;h2 id=&quot;understanding-the-docker-run-command&quot;&gt;Understanding the Docker Run Command&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;code&gt;docker run -d&lt;/code&gt; -&amp;gt; Run Docker in detached mode&lt;/li&gt;&lt;li&gt;&lt;code&gt;-p 80:8080&lt;/code&gt; -&amp;gt; Expose on port 80 of the host the port 8080 of the Docker container, so AdGuard Home sync web interface can be accessed  &lt;/li&gt;&lt;li&gt;&lt;code&gt;-v /docker/adguard-sync/adguardhome-sync.yaml:/config/adguardhome-sync.yaml&lt;/code&gt; -&amp;gt; serve the file at &lt;code&gt;/docker/adguard-sync/adguardhome-sync.yaml&lt;/code&gt; on the host machine as &lt;code&gt;/config/adguardhome-sync.yaml&lt;/code&gt; in the docker container&lt;/li&gt;&lt;li&gt;&lt;code&gt;--restart always&lt;/code&gt; -&amp;gt; Always restart the container  &lt;/li&gt;&lt;li&gt;&lt;code&gt;--name=adguardhome-sync&lt;/code&gt; -&amp;gt; The name that will be assigned to the container  &lt;/li&gt;&lt;li&gt;&lt;code&gt;ghcr.io/bakito/adguardhome-sync:latest&lt;/code&gt; -&amp;gt; The Docker image to use to create the container&lt;/li&gt;&lt;/ol&gt;&lt;h2 id=&quot;maintenance&quot;&gt;Maintenance&lt;/h2&gt;&lt;p&gt;Your docker container will restart automatically in case of issues or reboot thanks to &lt;code&gt;--restart always&lt;/code&gt;. To manually stop and restart it, you can use &lt;code&gt;docker stop adguardhome-sync&lt;/code&gt; and &lt;code&gt;docker restart adguardhome-sync&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Now stop replicating AdGuard Home settings manually and enjoy its protection! 🚀&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Self-Hosting changedetection.io with Docker and Proxmox]]></title><description><![CDATA[This time, let's explore the process of deploying  changedetection.io  as a Docker instance on a Proxmox home server. What's changedetection…]]></description><link>https://www.emanuelepapa.dev/self-hosting-changedetection.io-with-docker-and-proxmox/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/self-hosting-changedetection.io-with-docker-and-proxmox/</guid><category><![CDATA[Smart Home]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Fri, 01 Dec 2023 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/3313e167ece4b5ca157ebbf0dc82ed15/ross-findon-mG28olYFgHI-unsplash.jpg" length="1432912" type="image/jpg"/><content:encoded>&lt;p&gt;This time, let’s explore the process of deploying &lt;a href=&quot;https://github.com/dgtlmoon/changedetection.io&quot;&gt;changedetection.io&lt;/a&gt; as a Docker instance on a Proxmox home server.&lt;/p&gt;&lt;h1 id=&quot;whats-changedetectionio&quot;&gt;What’s changedetection.io&lt;/h1&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/dgtlmoon/changedetection.io&quot;&gt;changedetection.io&lt;/a&gt; is a simple and free open source website change detection, website watcher, restock monitor and notification service.&lt;br/&gt;
It can detect website content changes and perform meaningful actions - trigger notifications via Discord, Email, Slack, Telegram, API calls and many more.
We want to be able to check changes also on webpages that uses JavaScript to fill-in the content, so we need to install a &lt;a href=&quot;https://www.selenium.dev/documentation/webdriver/&quot;&gt;WebDriver&lt;/a&gt; as well.&lt;/p&gt;&lt;h1 id=&quot;installation-steps&quot;&gt;Installation steps&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;Ensure you have a Proxmox home server set up and running&lt;/li&gt;&lt;li&gt;Use the Proxmox main shell to create an Alpine Docker LXC using &lt;a href=&quot;https://github.com/tteck/Proxmox/raw/main/ct/alpine-docker.sh&quot;&gt;this awesome script&lt;/a&gt; from &lt;a href=&quot;https://tteck.github.io/Proxmox/&quot;&gt;tteck&lt;/a&gt;. Customize the installation and set 4GB disk space.&lt;/li&gt;&lt;li&gt;In the shell of the newly created container, run the following Docker command to run Chrome WebDriver:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;docker run -d \
  --name selenium \
  --restart always \
  -p 4444:4444 \
  --shm-size=&amp;quot;2g&amp;quot; \
  selenium/standalone-chrome-debug:3.141.59
&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;4&quot;&gt;&lt;li&gt;In the shell of the newly created container, run the following Docker command to run changedetection.io:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;docker run -d \
  --name changedetectionio \
  --restart always \
  --link selenium \
  -p 80:5000 \
  -e WEBDRIVER_URL=&amp;quot;http://selenium:4444/wd/hub&amp;quot; \
  -v /docker/changedetectionio/datastore:/datastore \
  dgtlmoon/changedetection.io
&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;4&quot;&gt;&lt;li&gt;Once the containers are up and running, you can access the changedetection.io web interface by navigating to &lt;code&gt;http://&amp;lt;your-container-ip&amp;gt;&lt;/code&gt; in your web browser.&lt;/li&gt;&lt;li&gt;You are all set!&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Note: &lt;code&gt;WEBDRIVER_URL&lt;/code&gt; is using &lt;code&gt;selenium&lt;/code&gt; in the path which is the name of the container running the WebDriver. This works because Docker creates a network between containers.&lt;/p&gt;&lt;p&gt;I suggest you to read the changedetection.io &lt;a href=&quot;https://github.com/dgtlmoon/changedetection.io/wiki&quot;&gt;wiki&lt;/a&gt; to explore all the features of changedetection.io and to learn how to setup notifications on your own instance!&lt;/p&gt;&lt;p&gt;Enjoy your own changedetection.io instance! 🚀&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Self-Hosting Stirling-PDF with Docker and Proxmox]]></title><description><![CDATA[Let's explore the process of deploying  Stirling-PDF  as a Docker instance on a Proxmox home server. Proxmox, coupled with an Alpine Docker…]]></description><link>https://www.emanuelepapa.dev/self-hosting-stirling-pdf-with-docker-and-proxmox/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/self-hosting-stirling-pdf-with-docker-and-proxmox/</guid><category><![CDATA[Smart Home]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sat, 25 Nov 2023 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/7c0d2d84a1c3c0075d98a3dcf6d54ed8/jainath-ponnala-9wWX_jwDHeM-unsplash.jpg" length="2091264" type="image/jpg"/><content:encoded>&lt;p&gt;Let’s explore the process of deploying &lt;a href=&quot;https://github.com/Frooodle/Stirling-PDF&quot;&gt;Stirling-PDF&lt;/a&gt; as a Docker instance on a Proxmox home server.&lt;br/&gt;
Proxmox, coupled with an Alpine Docker LXC, provides a simple solution to isolate applications, making it an efficient choice for self-hosting.&lt;br/&gt;
This article aims to share my findings and guide you through the setup process.&lt;/p&gt;&lt;h1 id=&quot;whats-stirling-pdf&quot;&gt;What’s Stirling-PDF&lt;/h1&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/Frooodle/Stirling-PDF&quot;&gt;Stirling-PDF&lt;/a&gt; is a powerful locally hosted web based PDF manipulation tool that allows you to perform various operations on PDF files, such as splitting merging, converting, reorganizing, adding images, rotating, compressing, and more.&lt;/p&gt;&lt;h1 id=&quot;installation-steps&quot;&gt;Installation steps&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;Ensure you have a Proxmox home server set up and running&lt;/li&gt;&lt;li&gt;Use the Proxmox main shell to create an Alpine Docker LXC using &lt;a href=&quot;https://github.com/tteck/Proxmox/raw/main/ct/alpine-docker.sh&quot;&gt;this awesome script&lt;/a&gt; from &lt;a href=&quot;https://tteck.github.io/Proxmox/&quot;&gt;tteck&lt;/a&gt;. (Assign at least 4GB of disk space if you plan to install the &lt;a href=&quot;https://github.com/Frooodle/Stirling-PDF/blob/main/Version-groups.md&quot;&gt;full version of Stirling-PDF&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;In the shell of the newly created container, run the following Docker command:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;docker run -d \
-p 80:8080 \
-v /docker/stirlingpdf/ocrdata:/usr/share/tesseract-ocr/4.00/tessdata \
-v /docker/stirlingpdf/configs:/configs \
-e DOCKER_ENABLE_SECURITY=true \
-e SECURITY_ENABLE_LOGIN=true \
--restart always \
--name stirling-pdf \
frooodle/s-pdf:latest
&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;4&quot;&gt;&lt;li&gt;Once the container is up and running, you can access the Stirling-PDF web interface by navigating to &lt;code&gt;http://&amp;lt;your-container-ip&amp;gt;&lt;/code&gt; in your web browser.&lt;/li&gt;&lt;li&gt;You are all set, but keep reading for more information!&lt;/li&gt;&lt;/ol&gt;&lt;h2 id=&quot;understanding-the-docker-run-command&quot;&gt;Understanding the Docker Run Command&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;code&gt;docker run -d&lt;/code&gt; -&amp;gt; Run Docker in detached mode&lt;/li&gt;&lt;li&gt;&lt;code&gt;-p 80:8080&lt;/code&gt; -&amp;gt; Expose on port 80 of the host the port 8080 of the Docker container, so Stirling-PDF can be accessed  &lt;/li&gt;&lt;li&gt;&lt;code&gt;-v /docker/stirlingpdf/ocrdata:/usr/share/tesseract-ocr/4.00/tessdata&lt;/code&gt; -&amp;gt; mounts the folder at &lt;code&gt;/docker/stirlingpdf/ocrdata&lt;/code&gt; on the host machine to &lt;code&gt;/usr/share/tesseract-ocr/4.00/tessdata&lt;/code&gt; in the docker container  &lt;/li&gt;&lt;li&gt;&lt;code&gt;-v /docker/stirlingpdf/configs:/configs&lt;/code&gt; -&amp;gt; Mount the folder at &lt;code&gt;/docker/stirlingpdf/configs&lt;/code&gt; on the host machine to &lt;code&gt;/configs&lt;/code&gt; in the docker container  &lt;/li&gt;&lt;li&gt;&lt;code&gt;-e DOCKER_ENABLE_SECURITY=true&lt;/code&gt; -&amp;gt; Tell docker to download security jar (required as true for auth login)  &lt;/li&gt;&lt;li&gt;&lt;code&gt;-e SECURITY_ENABLE_LOGIN=true&lt;/code&gt; -&amp;gt; Actually enable the authentication  &lt;/li&gt;&lt;li&gt;&lt;code&gt;--restart always&lt;/code&gt; -&amp;gt; Always restart the container  &lt;/li&gt;&lt;li&gt;&lt;code&gt;--name stirling-pdf&lt;/code&gt; -&amp;gt; The name that will be assigned to the container  &lt;/li&gt;&lt;li&gt;&lt;code&gt;frooodle/s-pdf:latest&lt;/code&gt; -&amp;gt; The Docker image to use to create the container&lt;/li&gt;&lt;/ol&gt;&lt;h2 id=&quot;security&quot;&gt;Security&lt;/h2&gt;&lt;p&gt;Enabling &lt;code&gt;DOCKER_ENABLE_SECURITY&lt;/code&gt; and &lt;code&gt;SECURITY_ENABLE_LOGIN&lt;/code&gt; is crucial to enforce authentication in Stirling-PDF.&lt;br/&gt;
You can always customize security stuff by changing the &lt;code&gt;settings.yaml&lt;/code&gt; file created inside &lt;code&gt;/docker/stirlingpdf/configs&lt;/code&gt; instead of providing these environment variables.&lt;/p&gt;&lt;h2 id=&quot;ocr&quot;&gt;OCR&lt;/h2&gt;&lt;p&gt;If you need additional languages for OCR, using the LXC shell move to &lt;code&gt;/docker/stirlingpdf/ocrdata&lt;/code&gt; and download the needed language file from &lt;a href=&quot;https://github.com/tesseract-ocr/tessdata/tree/main&quot;&gt;tesseract-ocr/tessadata repository&lt;/a&gt;.&lt;br/&gt;
For example, run &lt;code&gt;wget https://github.com/tesseract-ocr/tessdata/raw/main/ita.traineddata&lt;/code&gt; for the Italian language. &lt;/p&gt;&lt;p&gt;If you don’t need OCR or you run on low resources, depending on the feature you need, you might want to use a lightweight version, in that case change &lt;code&gt;latest&lt;/code&gt; to &lt;code&gt;latest-lite&lt;/code&gt; or &lt;code&gt;latest-ultra-lite&lt;/code&gt;.&lt;br/&gt;
See &lt;a href=&quot;https://github.com/Frooodle/Stirling-PDF/blob/main/Version-groups.md&quot;&gt;here&lt;/a&gt; for a features overview.&lt;/p&gt;&lt;h2 id=&quot;maintenance&quot;&gt;Maintenance&lt;/h2&gt;&lt;p&gt;Your docker container will restart automatically in case of issues or reboot thanks to &lt;code&gt;--restart always&lt;/code&gt;. To manually stop and restart it, you can use &lt;code&gt;docker stop stirling-pdf&lt;/code&gt; and &lt;code&gt;docker restart stirling-pdf&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Enjoy your own Stirling-PDF instance! 🚀&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Kotlin Multiplatform and Swift - Overcoming Interoperability Challenges for Multiplatform Development]]></title><description><![CDATA[Kotlin Multiplatform Mobile (KMM)  is a popular framework for developing multi-platform mobile applications with shared code between Android…]]></description><link>https://www.emanuelepapa.dev/kotlin-multiplatform-and-swift-overcoming-interoperability-challenges-for-multiplatform-development/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/kotlin-multiplatform-and-swift-overcoming-interoperability-challenges-for-multiplatform-development/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 16 Jul 2023 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/5e38a4a7f7cc96084cc371866f490714/jonathan-borba-xRDuEeG1TVI-unsplash.jpg" length="6225512" type="image/jpg"/><content:encoded>&lt;p&gt;&lt;a href=&quot;https://kotlinlang.org/lp/multiplatform/&quot;&gt;Kotlin Multiplatform Mobile (KMM)&lt;/a&gt; is a popular framework for developing multi-platform mobile applications with shared code between Android and iOS.
However, one limitation of KMM is that it doesn’t allow direct use of Swift libraries: this is because Kotlin doesn’t have direct interoperability with the Swift language; instead, it relies on interoperability with Objective-C.&lt;/p&gt;&lt;p&gt;As a result, KMM doesn’t have built-in support for Swift-only libraries that don’t have an Objective-C API. This means that developers can’t use Swift libraries directly in a KMM project without first creating a bridging library that provides a Kotlin interface to the Swift code.&lt;/p&gt;&lt;p&gt;Creating a bridging library requires writing a separate library for each Swift library that needs to be used in the project: this can be time-consuming and requires knowledge of both Kotlin and Swift.&lt;/p&gt;&lt;h2 id=&quot;using-a-swift-library-with-dependency-injection-and-koin&quot;&gt;Using a Swift library with Dependency Injection and Koin&lt;/h2&gt;&lt;p&gt;Imagine the classical situation where you want to use a third party service and they provide you a native Android SDK and a native iOS SDK (written in Swift).
If you are going to develop two native apps, you face no issues, each platform implements and uses their respective SDK.
If you are going to develop a KMM app, you end up having some issues due to the fact the iOS SDK is written in Swift.&lt;/p&gt;&lt;p&gt;Let’s see how we can use &lt;a href=&quot;https://insert-koin.io/&quot;&gt;Koin&lt;/a&gt; to achieve Dependency Injection (DI) and incorporate a third-party iOS Swift SDK in a KMM project.
Koin supports KMM development, making it the ideal choice for KMM projects.&lt;/p&gt;&lt;h1 id=&quot;shared-code&quot;&gt;Shared code&lt;/h1&gt;&lt;p&gt;If you are going to use a library which already supports KMM, you are going to declare it as a dependency in the &lt;code&gt;commonMain&lt;/code&gt; source set of the &lt;code&gt;shared&lt;/code&gt; module &lt;code&gt;build.gradle.kts&lt;/code&gt; file.
This is not our case, we have a library for Android and a separate one for iOS.&lt;br/&gt;
For the sake of example, let’s imagine we have &lt;code&gt;com.weather.sdk:1.0.0&lt;/code&gt; for Android and &lt;code&gt;WeatherSDK&lt;/code&gt; for iOS: they provide weather information given a city.&lt;/p&gt;&lt;p&gt;So what do we do?&lt;/p&gt;&lt;h2 id=&quot;create-a-wrapper&quot;&gt;Create a wrapper&lt;/h2&gt;&lt;p&gt;To avoid scattering the use of the weather SDK throughout the project and allow for future flexibility to swap it out with another SDK, we create a wrapper interface for it. This approach provides a layer of abstraction that enables us to decouple the SDK implementation from the rest of the project, making it easier to maintain and modify in the future.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;interface WeatherDataSource {
    suspend fun getWeather(cityName: String): WeatherInfo
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Do not take care of the function signature, it’s just an example. Keep the focus on the fact we are creating an interface to decouple from the Weather SDK.&lt;/p&gt;&lt;p&gt;Now, we need to implement this interface both for Android and for iOS. The implementantions will use the respective Weather SDK to fetch data.
Let’s start from Android first, which is easier.&lt;/p&gt;&lt;h2 id=&quot;android-setup&quot;&gt;Android setup&lt;/h2&gt;&lt;p&gt;First, set the Android library as dependency in the &lt;code&gt;androidMain&lt;/code&gt; source set.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val androidMain by getting {
    dependencies {
        implementation(&amp;quot;com.weather.sdk:1.0.0&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This makes us able to use &lt;code&gt;com.weather.sdk&lt;/code&gt; into the &lt;code&gt;androidMain&lt;/code&gt; source set.&lt;/p&gt;&lt;p&gt;Next step is to create the implementation for &lt;code&gt;WeatherDataSource&lt;/code&gt;, so we create &lt;code&gt;AndroidWeatherDataSource&lt;/code&gt;, where we use the &lt;code&gt;weatherSdk&lt;/code&gt; class of Weather SDK to fetch weather information.&lt;br/&gt;
&lt;em&gt;This is the link between our code and the third party SDK.&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class AndroidWeatherDataSource(
    private val weatherSdk: WeatherSDK
): WeatherDataSource {
    suspend fun getWeather(cityName: String): WeatherInfo {
        //use weatherSdk to fetch weather information!
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, still in the &lt;code&gt;androidMain&lt;/code&gt; source set, we create this function to init Koin:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;/**
 * [context] is the Android app context
 * [appModules] is a list of modules defined in the app layer (e.g. viewModels)
 */
fun initKoin(context: Context, appModules: List&amp;lt;Module&amp;gt;) {
    startKoin {
        androidLogger()
        androidContext(context)
        modules(appModules + sharedModules + weatherModule)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Koin will be setup using:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;appModules&lt;/code&gt;: modules created in the app layer, e.g. for viewModels&lt;/li&gt;&lt;li&gt;&lt;code&gt;sharedModules&lt;/code&gt;: modules created in the &lt;code&gt;commonMain&lt;/code&gt; source set to be shared between platforms&lt;/li&gt;&lt;li&gt;&lt;code&gt;weatherModule&lt;/code&gt;: module where we declare dependencies for &lt;code&gt;com.weather.sdk&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We will concentrate on &lt;code&gt;weatherModule&lt;/code&gt;, let’s see its code:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val weatherModule = module {
    singleOf(::AndroidWeatherDataSource) bind WeatherDataSource::class
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We declare &lt;code&gt;AndroidWeatherDataSource&lt;/code&gt; as single instance for whenever we require &lt;code&gt;WeatherDataSource&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Now, let’s move to iOS!&lt;/p&gt;&lt;h2 id=&quot;ios-setup&quot;&gt;iOS setup&lt;/h2&gt;&lt;p&gt;iOS setup it’s a little bit more cumbersome than the Android one, but let’s go step by step to see what’s needed.&lt;/p&gt;&lt;p&gt;Since the iOS library is written in Swift and doesn’t have Objective-C bindings, we can’t declare it in the &lt;code&gt;iOSMain&lt;/code&gt; source set, we can’t &lt;a href=&quot;https://kotlinlang.org/docs/native-c-interop.html&quot;&gt;&lt;code&gt;cinterop&lt;/code&gt;&lt;/a&gt; it neither.
The only way to use it is from the iOS project that AndroidStudio/KMMPlugin created for us.&lt;/p&gt;&lt;p&gt;First thing, as we did for Android, it’s to create the implementation for &lt;code&gt;WeatherDataSource&lt;/code&gt;, so we create &lt;code&gt;IOSWeatherDataSource&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import WeatherSDK
import shared

class IOSWeatherDataSource: WeatherDataSource {
    final let weatherSDK: WeatherSDK

    init(weatherSDK: WeatherSDK) {
        self.weatherSDK = weatherSDK
    }

    func getWeather(cityName: String) async throws -&amp;gt; WeatherInfo {
        //use weatherSdk to fetch weather information!
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, let’s go back to our &lt;code&gt;shared&lt;/code&gt; module into &lt;code&gt;iOSMain&lt;/code&gt; source set: here, as well as for Android, we need to create a function to init Koin.
Right now, Koin API can’t be used in the iOS project, so we need both to create a function to init Koin and to expose all Koin dependencies to the iOS project.&lt;br/&gt;
To do so we create a class &lt;code&gt;DependenciesProviderHelper&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class DependenciesProviderHelper : KoinComponent {

    /**
     * [weatherDataSource] is the iOS implementation to fetch weather information
     */
    initKoin(
        weatherDataSource: WeatherDataSource
    ) {
        val iosDependenciesModule = module {
            single {
                weatherDataSource
            } bind WeatherDataSource::class
        }
        startKoin {
            modules(iosDependenciesModule + sharedModules)
        }
    }

    //usecases
    val getWeatherInfoUseCase: GetWeatherInfoUseCase by inject()

}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since it’s currently not possible to use Koin APIs from iOS, the &lt;code&gt;initKoin()&lt;/code&gt; function can’t have a list of Koin &lt;code&gt;Module&lt;/code&gt; like the Android one. However, we can still provide the real implementations of our dependencies and create a Koin module in the shared code. Then, we can use &lt;code&gt;startKoin()&lt;/code&gt; with both the iOS dependencies and the shared modules.&lt;/p&gt;&lt;p&gt;Imagine then that this &lt;em&gt;DataSource&lt;/em&gt; will be used in a &lt;em&gt;Repository&lt;/em&gt; that will be used in a &lt;em&gt;UseCase&lt;/em&gt;, here in &lt;code&gt;DependenciesProviderHelper&lt;/code&gt; we also expose &lt;code&gt;getWeatherInfoUseCase&lt;/code&gt; to be able to use it from the iOS project.&lt;/p&gt;&lt;h2 id=&quot;start-koin&quot;&gt;Start Koin!&lt;/h2&gt;&lt;p&gt;Both the projects are now setup correctly, Dependency Injection is in place, real implementations have been created, we just miss one last thing: to finally start Koin!&lt;/p&gt;&lt;p&gt;On Android, you start Koin in your application class:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class MyApplication : Application() {
    
    override fun onCreate() {
        super.onCreate()
        initKoin(this@MyApplication, appModules)
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On iOS, you do the same:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@main
struct MyApplication: App {

    init() {
        let iosWeatherDataSource = //create iOSWeatherDataSource
        //DependenciesProviderHelper should be a singleton
        DependenciesProviderHelper().initKoin(
            weatherDataSource: iosWeatherDataSource
        )
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;instances-injected-by-koin&quot;&gt;Instances injected by Koin&lt;/h1&gt;&lt;p&gt;What instances does Koin provide in our code and how does it provide them, based on the code above?
&lt;code&gt;AndroidWeatherDataSource&lt;/code&gt; is defined as a single instance for &lt;code&gt;WeatherDataSource&lt;/code&gt; on Android, which means that Koin will handle its instantiation and provide it whenever it is needed in our code. Because it is defined as single, the instance will remain the same throughout the lifecycle of our app.
On iOS, the approach is similar, but Koin will not instantiate &lt;code&gt;IOSWeatherDataSource&lt;/code&gt; on its own. Instead, we will need to instantiate it ourselves and provide the instance to &lt;code&gt;startKoin()&lt;/code&gt; before Koin can manage it and provide it whenever it is needed.&lt;/p&gt;&lt;h1 id=&quot;considerations&quot;&gt;Considerations&lt;/h1&gt;&lt;p&gt;We have come to the end of how to use a Swift third-party library in a Kotlin Multiplatform project. While it’s still necessary to write two different native implementations (one for iOS and one for Android), these implementations are restricted in the &lt;em&gt;DataSource&lt;/em&gt; level, while &lt;em&gt;UseCases&lt;/em&gt; and &lt;em&gt;Repositories&lt;/em&gt; can still be shared and written into the &lt;code&gt;commonMain&lt;/code&gt; source set.&lt;/p&gt;&lt;p&gt;This means that the &lt;em&gt;UseCases&lt;/em&gt; can use shared &lt;em&gt;Repositories&lt;/em&gt;, and tests can be written to cover the &lt;em&gt;UseCases&lt;/em&gt; and &lt;em&gt;Repositories&lt;/em&gt; code just once. If there’s a bug in the &lt;em&gt;DataSources&lt;/em&gt;, it will be spotted by these tests.&lt;/p&gt;&lt;p&gt;The shared code also provides the advantage of being able to implement functionality once, such as a cache in the &lt;em&gt;Repository&lt;/em&gt; layer, instead of writing it twice for both platforms.&lt;/p&gt;&lt;p&gt;While there are limitations and challenges to using Swift third-party libraries in a Kotlin Multiplatform project, the benefits can make it a worthwhile investment for developers looking to build multiplatform apps.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Easily retrieve SHA fingerprints with Gradle signingReport task for Android development]]></title><description><![CDATA[Signing applications with a digital certificate before releasing them to the public helps to protect the integrity and security of your app…]]></description><link>https://www.emanuelepapa.dev/easily-retrieve-sha-fingerprints-with-gradle-signingreport-task-for-android-development/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/easily-retrieve-sha-fingerprints-with-gradle-signingreport-task-for-android-development/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Wed, 22 Mar 2023 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/6c5c7ce705f7e6e51c2ee1443c3377b8/pixabay-dirt-88534.jpeg" length="1091517" type="image/jpeg"/><content:encoded>&lt;p&gt;Signing applications with a digital certificate before releasing them to the public helps to protect the integrity and security of your app, as well as establish your identity as the developer.&lt;/p&gt;&lt;p&gt;In the world of Android development, the Gradle build tool provides a convenient way to manage the signing process for your apps: one useful feature of Gradle is the &lt;code&gt;signingReport&lt;/code&gt; task, which allows you to generate a report that lists all of the signing configurations for your app.&lt;br/&gt;
This might become useful whenever you need to get the SHA-1 or SHA-256 of your signing key to authenticate against a service.&lt;/p&gt;&lt;p&gt;To use the this task, simply navigate to your project’s root directory in the terminal and run the following command:&lt;/p&gt;&lt;p&gt;&lt;code&gt;./gradlew signingReport&lt;/code&gt;&lt;/p&gt;&lt;p&gt;The generated report includes information such as the signing configuration used for each variant, the location of the signing artifacts, and the SHA fingerprints of the signing certificates.&lt;/p&gt;&lt;p&gt;Another benefit of using this task is that it can help you identify any issues with your signing configuration: for example, if you see a variant listed in the report that is not properly signed, you can use the information provided in the report to troubleshoot the issue and get your app properly signed.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Boost your Internet speed setting up a 2.5Gbps Ethernet connection with iliad FTTH and a Macbook Pro M1]]></title><description><![CDATA[Setting up a 2.5Gbps Ethernet network can significantly improve your Internet speed, allowing you to enjoy faster downloads and smoother…]]></description><link>https://www.emanuelepapa.dev/boost-your-internet-speed-setting-up-a-2.5gbps-ethernet-connection-with-iliad-ftth-and-a-macbook-pro-m1/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/boost-your-internet-speed-setting-up-a-2.5gbps-ethernet-connection-with-iliad-ftth-and-a-macbook-pro-m1/</guid><category><![CDATA[Networking]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Tue, 21 Mar 2023 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/a361f1f3f63ea077ae67492c3bd5939a/fiber_cable_pxfuel.jpeg" length="901305" type="image/jpeg"/><content:encoded>&lt;p&gt;Setting up a 2.5Gbps Ethernet network can significantly improve your Internet speed, allowing you to enjoy faster downloads and smoother streaming.
In this blog post, I’ll share with you what steps I followed to set up a 2.5Gbps Internet connection with iliad FTTH (Open Fiber) and a Macbook Pro M1.&lt;/p&gt;&lt;p&gt;Let’s start, this is what you need:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A Macbook Pro M1 (this is my case, but other hardware can work as well)&lt;/li&gt;&lt;li&gt;A FTTH connection with iliad using their iliadbox which supports 2.5Gbps on a single Ethernet port&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.amazon.it/dp/B00VFN6XHK?psc=1&amp;amp;ref=ppx_yo2ov_dt_b_product_details&quot;&gt;Cat-6a keystone&lt;/a&gt; to complete the &lt;a href=&quot;https://www.fibernet.it/portfolio/hybrid-termination-box/?lang=en&quot;&gt;Open Fiber termination box&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.amazon.it/dp/B09Q5GGM9L?psc=1&amp;amp;ref=ppx_yo2ov_dt_b_product_details&quot;&gt;2.5Gbps Ethernet adapter&lt;/a&gt; to have a 2.5Gbps port&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.amazon.it/dp/B00YT5B3NK?psc=1&amp;amp;ref=ppx_yo2ov_dt_b_product_details&quot;&gt;Ethernet wall plate&lt;/a&gt; if you want to access the Ethernet in a different room than the one where your iliadbox is&lt;/li&gt;&lt;li&gt;Some meters of Ethernet cable. Cat-6a would be awesome but Cat-5e still perfectly does the job!&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Now, let’s get started with the setup process.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Run a cable from the room where the iliadbox is to where you want to have another Ethernet wall plate&lt;/li&gt;&lt;li&gt;Mount the Ethernet wall plate on the wall and attach the Ethernet cable to it following the &lt;a href=&quot;https://en.wikipedia.org/wiki/ANSI/TIA-568#T568A_and_T568B_termination&quot;&gt;T568B standard&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Attach the keystone to the termination box and attach the Ethernet cable to it following the &lt;a href=&quot;https://en.wikipedia.org/wiki/ANSI/TIA-568#T568A_and_T568B_termination&quot;&gt;T568B standard&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Attach an Ethernet cable from the keystone to the iliadbox 2.5Gbps port&lt;/li&gt;&lt;li&gt;Attach an Ethernet cable from the wall plate to the 2.5Gbps Ethernet adapter&lt;/li&gt;&lt;li&gt;Attach the Ethernet adapter to the Macbook using a Thunderbolt/USB-C port&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Once you’ve completed the above steps, it’s time to test your connection.&lt;br/&gt;
Disable the Wi-Fi on your Macbook and run a speed test to see if you’re getting the expected speed.&lt;/p&gt;&lt;p&gt;Congratulations, you’ve successfully set up a 2.5Gbps Internet connection with Iliad (Open Fiber) and a Macbook Pro M1!&lt;br/&gt;
Enjoy your faster internet speed and improved online experience 🚀&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Kotlin Essentials - My experience reviewing the latest Marcin Moskala book]]></title><description><![CDATA[The 6th of May 2022 was a typical day for me, as I was surfing the web and reading the latest tech news until a huge opportunity presented…]]></description><link>https://www.emanuelepapa.dev/kotlin-essentials-my-experience-reviewing-the-latest-marcin-moskala-book/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/kotlin-essentials-my-experience-reviewing-the-latest-marcin-moskala-book/</guid><category><![CDATA[Info]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Mon, 09 Jan 2023 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/4601ba403d72863222017e0729a227aa/kotlin-essentials-my-experience-reviewing-the-latest-marcin-moskala-book.png" length="366996" type="image/png"/><content:encoded>&lt;p&gt;The 6th of May 2022 was a typical day for me, as I was surfing the web and reading the latest tech news until a huge opportunity presented itself to me when I read &lt;a href=&quot;https://blog.kotlin-academy.com/kotlin-for-developers-technical-reviewers-wanted-97ae6ed542b1&quot;&gt;an article saying Marcin Moskala was looking for someone to review his upcoming Kotlin book&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Almost immediately I decided to apply to be one of the reviewers!&lt;/p&gt;&lt;p&gt;As time passed, maybe I forgot about that moment until I received an email saying I was selected for the reviewing program!
It was exciting news for me to be invited to serve as a reviewer for Marcin Moskala’s upcoming book on Kotlin.
As a software developer with a strong interest in Kotlin and its capabilities, I was excited to have the opportunity to contribute my thoughts and feedback to the book.&lt;/p&gt;&lt;p&gt;So I spent most of my free time over the last few months reading every chapter of the book, discussing topics with other reviewers, paying attention to code samples, and again reading every chapter of the book.&lt;/p&gt;&lt;p&gt;One of the things that I particularly appreciated about the book was its clear and concise writing style. Marcin does an excellent job of explaining complex concepts in a way that is easy to understand. The book is also well-organized, with each chapter building upon the knowledge and skills developed in previous ones.
This makes it easy for newcomers to follow along and absorb the material.&lt;/p&gt;&lt;p&gt;This book covers the essentials of Kotlin and those coming from other languages who wish to learn Kotlin would find this book extremely useful.&lt;/p&gt;&lt;p&gt;You can get it on &lt;a href=&quot;https://leanpub.com/kotlin_developers&quot;&gt;Leanpub&lt;/a&gt; if you want a digital version or on &lt;a href=&quot;https://a.co/d/70xWmis&quot;&gt;Amazon&lt;/a&gt; if you prefer a paperback version!&lt;/p&gt;&lt;p&gt;The opportunity to review the book was a pleasure for me.
I hope to have the chance to give my feedback on more publications in the near future.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Speaking about KMM at Commit University in November 2022]]></title><description><![CDATA[A few days ago I gave a talk at  Commit University  in Florence, Italy. I spoke about  Kotlin Multiplatform Mobile (KMM)  detailing some of…]]></description><link>https://www.emanuelepapa.dev/speaking-about-kmm-at-commit-university-in-november-2022/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/speaking-about-kmm-at-commit-university-in-november-2022/</guid><category><![CDATA[Info]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 20 Nov 2022 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/edc39180b01f5f3258f80fce5c7b371b/emanuele_papa_speaker_commit_university_november_2022.jpeg" length="249883" type="image/jpeg"/><content:encoded>&lt;p&gt;A few days ago I gave a talk at &lt;a href=&quot;https://www.commitsoftware.it/kotlin-multiplatform-mobile-how-it-works/&quot;&gt;Commit University&lt;/a&gt; in Florence, Italy.&lt;/p&gt;&lt;p&gt;I spoke about &lt;a href=&quot;https://kotlinlang.org/lp/mobile/&quot;&gt;Kotlin Multiplatform Mobile (KMM)&lt;/a&gt; detailing some of the struggles that affect your developer experience while mixing Kotlin and Swift, and how to work around them.  &lt;/p&gt;&lt;p&gt;I met a lot of new people interested in the topic and was fun to discuss it!&lt;/p&gt;&lt;p&gt;You can see a recording of the talk on &lt;a href=&quot;https://www.youtube.com/watch?v=SRP3nnAQ5P4&quot;&gt;YouTube&lt;/a&gt;. It was given in Italian, I’m sorry this time for the English followers.
Anyway, you can see the slides on &lt;a href=&quot;https://speakerdeck.com/emanuelepapa/commit-university-november-2022-kmm-survival-guide-how-to-tackle-everyday-struggles-between-kotlin-and-swift&quot;&gt;SpeakerDeck&lt;/a&gt; and the &lt;a href=&quot;https://github.com/ema987/kmm-survival-guide-samples&quot;&gt;samples sourcecode&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Thank you to all the people I met and to the ones who attended my talk!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Solve ADB stuck on waiting for debugger]]></title><description><![CDATA[While debugging an Android app you are used to seeing the  Waiting for debugger  dialog.
This dialog is shown until the debugger attaches to…]]></description><link>https://www.emanuelepapa.dev/solve-adb-stuck-on-waiting-for-debugger/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/solve-adb-stuck-on-waiting-for-debugger/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 06 Nov 2022 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/debd29a8826390d3a50d51abd5935afc/fog-2203650_1920.jpg" length="113958" type="image/jpg"/><content:encoded>&lt;p&gt;While debugging an Android app you are used to seeing the &lt;em&gt;Waiting for debugger&lt;/em&gt; dialog.
This dialog is shown until the debugger attaches to the process of the app you want to debug, to let you debug it.&lt;/p&gt;&lt;p&gt;Sometimes, for various unknown and strange reasons, may happen that this dialog won’t go away, and so you are unable to continue your debugging session.&lt;/p&gt;&lt;p&gt;A solution could be to reboot your device/emulator and also kill/restart Android Studio.
This is quite a heavy solution that makes you lose a lot of time.&lt;/p&gt;&lt;p&gt;You can avoid all these restarts just by running the following code in your terminal:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;adb shell am clear-debug-app
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Doing so should clear any broken reference that &lt;em&gt;ADB&lt;/em&gt; might have with your app, so you can now launch a new debug session and you will see the infamous dialog to disappear!&lt;/p&gt;&lt;p&gt;Happy coding!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Speaking about KMM at droidcon Italy 2022]]></title><description><![CDATA[A few days ago I took part in  droidcon Italy  as a speaker. I spoke about  Kotlin Multiplatform Mobile (KMM)  detailing some of the…]]></description><link>https://www.emanuelepapa.dev/speaking-about-kmm-at-droidcon-italy-2022/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/speaking-about-kmm-at-droidcon-italy-2022/</guid><category><![CDATA[Info]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 16 Oct 2022 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/5313fd9b5f921a5f53f9a3e42cf2b26e/emanuele_papa_speaker_droidcon_italy_2022.jpeg" length="149172" type="image/jpeg"/><content:encoded>&lt;p&gt;A few days ago I took part in &lt;a href=&quot;https://it.droidcon.com/2022/speakers/&quot;&gt;droidcon Italy&lt;/a&gt; as a speaker.&lt;/p&gt;&lt;p&gt;I spoke about &lt;a href=&quot;https://kotlinlang.org/lp/mobile/&quot;&gt;Kotlin Multiplatform Mobile (KMM)&lt;/a&gt; detailing some of the struggles that affect your developer experience while mixing Kotlin and Swift, and how to work around them.&lt;br/&gt;
I’ve also participated in the KMM Roundtable where I and other cool developers spoke about our KMM experiences. It was fun and interesting at the same time!&lt;/p&gt;&lt;p&gt;You can see the slides on &lt;a href=&quot;https://speakerdeck.com/emanuelepapa/droidcon-italy-2022-kmm-survival-guide-how-to-tackle-everyday-struggles-between-kotlin-and-swift&quot;&gt;SpeakerDeck&lt;/a&gt;.
Those of you who bought the ticket for the event can already see the recording of the talk, and so I want to share the &lt;a href=&quot;https://github.com/ema987/kmm-survival-guide-samples&quot;&gt;samples sourcecode&lt;/a&gt;.&lt;br/&gt;
Edit(16/07/2023): finally the video of the talk has been published on YouTube, see it &lt;a href=&quot;https://www.youtube.com/watch?v=VI7eWTfHw6I&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;&lt;p&gt;Furthermore, JetBrains just announced that KMM just moved from Alpha to Beta, so give it a chance and try it if you haven’t already!&lt;/p&gt;&lt;p&gt;There was a lot of interest around KMM: to the ones who didn’t have a chance to talk with me, feel free to write me a message, I’ll be happy to discuss it!&lt;/p&gt;&lt;p&gt;Thank you to all the people I met and to the ones who attended my talk!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Meet me at droidcon Italy 2022]]></title><description><![CDATA[A year has passed and it seems this blog was lost in time.
It has been a year full of tasks to accomplish in my private life, so the time…]]></description><link>https://www.emanuelepapa.dev/meet-me-at-droidcon-italy-2022/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/meet-me-at-droidcon-italy-2022/</guid><category><![CDATA[Info]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Thu, 22 Sep 2022 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/1c06a04c3ef41d87911f9c6426e18fe8/droidcon-italy-2022.png" length="174676" type="image/png"/><content:encoded>&lt;p&gt;A year has passed and it seems this blog was lost in time.
It has been a year full of tasks to accomplish in my private life, so the time for side projects decreased and I didn’t have the time to keep the quality level I want for posts I publish, so practically all of them remained drafts.
I hope I’ll resume publishing soon but in the meanwhile…&lt;/p&gt;&lt;p&gt;I’m happy to say that I’m very excited to be speaking at &lt;a href=&quot;https://it.droidcon.com/&quot;&gt;droidcon Italy 2022&lt;/a&gt; in a few days!&lt;/p&gt;&lt;p&gt;Last year I talked about Flutter at &lt;a href=&quot;/flutter-code-generation-with-paddinger-at-droidcon-london-2021&quot;&gt;droidcon London 2021&lt;/a&gt;, but then I moved to use &lt;a href=&quot;https://kotlinlang.org/lp/mobile/&quot;&gt;Kotlin Multiplatform Mobile (KMM)&lt;/a&gt; daily.&lt;br/&gt;
So, this time, I will be speaking about what’s cool and what’s not about &lt;em&gt;KMM&lt;/em&gt;.
I do love &lt;em&gt;KMM&lt;/em&gt; but you anyway need to know that having a silver bullet for cross-platform development isn’t so easy, so things might get difficult sometimes. I will share some tricks to create a better synergy between Android and iOS!&lt;/p&gt;&lt;p&gt;You can find the &lt;a href=&quot;https://it.droidcon.com/2022/speakers/&quot;&gt;talk abstract here on the speakers page&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Please like and retweet the &lt;a href=&quot;https://twitter.com/Droidconit/status/1572571609927667714&quot;&gt;Twitter post&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Come and say hi, I’ll be happy to meet you!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Android image and video picker using ActivityResultContracts in Jetpack Compose]]></title><description><![CDATA[Experimenting with Jetpack Compose you might have trouble doing old things in the new cool way. One of these things is opening a media…]]></description><link>https://www.emanuelepapa.dev/android-image-and-video-picker-using-activityresultcontracts-in-jetpack-compose/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/android-image-and-video-picker-using-activityresultcontracts-in-jetpack-compose/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Tue, 13 Sep 2022 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/94e7e3a046a451354d4dfb71d174109d/pexels-markus-spiske-4201333.jpeg" length="1157033" type="image/jpeg"/><content:encoded>&lt;p&gt;Experimenting with Jetpack Compose you might have trouble doing old things in the new cool way.&lt;/p&gt;&lt;p&gt;One of these things is opening a media picker where the user can select an image or a video: imagine the use case of a Social Media app where you want your users to publish their content.&lt;/p&gt;&lt;p&gt;Let’s make it simple for now: you want your users to be able to select an image from their gallery.&lt;/p&gt;&lt;p&gt;Working with Jetpack Compose, you can just do the following:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val pickMediaActivityResultLauncher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.GetContent()
) { uri: Uri? -&amp;gt;
    //use the received Uri
}

Button(onClick = {
    pickMediaActivityResultLauncher.launch(&amp;quot;image/*&amp;quot;)
}) {
    Text(text = &amp;quot;Pick media content&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, imagine you want your users to be able to publish a video.
You can just change the &lt;em&gt;MIME Type&lt;/em&gt; &lt;code&gt;image/*&lt;/code&gt; to &lt;code&gt;video/*&lt;/code&gt;, and keep the above code as it is.
In the picker which will appear, the users can only select a video from their gallery.&lt;/p&gt;&lt;p&gt;Cool, but this is a Social Media app and users want to publish both pictures and videos, and you don’t want to have two different buttons for them to choose what to pick from their gallery.
Unfortunately, the method &lt;code&gt;launch()&lt;/code&gt; doesn’t accept a &lt;code&gt;List&lt;/code&gt; of &lt;em&gt;MIME Types&lt;/em&gt; as a parameter.&lt;/p&gt;&lt;p&gt;To solve this inconvenience, you can create a subclass of &lt;code&gt;ActivityResultContracts.GetContent&lt;/code&gt; and set the &lt;code&gt;Intent&lt;/code&gt; to handle both &lt;code&gt;image/*&lt;/code&gt; and &lt;code&gt;video/*&lt;/code&gt; &lt;em&gt;MIME types&lt;/em&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class GetMediaActivityResultContract : ActivityResultContracts.GetContent() {

    override fun createIntent(context: Context, input: String): Intent {
        return super.createIntent(context, input).apply {
            // Force only images and videos to be selectable
            putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(&amp;quot;image/*&amp;quot;, &amp;quot;video/*&amp;quot;))
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once you made this new custom &lt;code&gt;ActivityResultContract&lt;/code&gt; you can just launch it like in the previous example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val pickMediaActivityResultLauncher = rememberLauncherForActivityResult(
    contract = GetMediaActivityResultContract()
) { uri: Uri? -&amp;gt;
    //use the received Uri
}

Button(onClick = {
    pickMediaActivityResultLauncher.launch(&amp;quot;*/*&amp;quot;)
}) {
    Text(text = &amp;quot;Pick media content&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pay attention, you can’t just pass an empty String to &lt;code&gt;pickMediaActivityResultLauncher.launch()&lt;/code&gt; otherwise the system will raise an &lt;code&gt;ActivityNotFoundException&lt;/code&gt; because it doesn’t know how to handle that &lt;code&gt;Intent&lt;/code&gt;.&lt;br/&gt;
You can use the wildcard &lt;code&gt;*/*&lt;/code&gt; &lt;em&gt;MIME Type&lt;/em&gt; to launch the &lt;code&gt;Intent&lt;/code&gt; and then your users will be forced to choose only between an image or a video because those are the &lt;em&gt;MIME types&lt;/em&gt; you specified in &lt;code&gt;GetMediaActivityResultContract&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Bonus: starting from Android 11 (API Level 30) a &lt;a href=&quot;https://developer.android.com/training/data-storage/shared/photopicker&quot;&gt;built-in Photo picker was introduced&lt;/a&gt;. I suggest you to check it out and use this picker on devices running on newer APIs handling different Android versions in your code.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Flutter code generation with Paddinger at droidcon London 2021]]></title><description><![CDATA[Speaking at  droidcon London  few weeks ago was a very cool experience, I met a lot of new people, old colleagues, friends, and I really…]]></description><link>https://www.emanuelepapa.dev/flutter-code-generation-with-paddinger-at-droidcon-london-2021/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/flutter-code-generation-with-paddinger-at-droidcon-london-2021/</guid><category><![CDATA[Info]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Thu, 02 Dec 2021 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/1f0135b011e25122e3777ebb6e9d6e79/emanuele_papa_speaker_droidcon_london_2021.jpeg" length="397655" type="image/jpeg"/><content:encoded>&lt;p&gt;Speaking at &lt;a href=&quot;https://www.london.droidcon.com/&quot;&gt;droidcon London&lt;/a&gt; few weeks ago was a very cool experience, I met a lot of new people, old colleagues, friends, and I really enjoyed it!&lt;/p&gt;&lt;p&gt;I spoke about &lt;a href=&quot;/flutter-padding-widgets-generator-with-paddinger/&quot;&gt;paddinger&lt;/a&gt;, a Flutter library I made to improve productivity while building UIs.&lt;/p&gt;&lt;p&gt;I want to share here the &lt;a href=&quot;https://speakerdeck.com/emanuelepapa/supercharge-flutter-declarative-ui-with-code-generation&quot;&gt;slides&lt;/a&gt; I used for the talk and the &lt;a href=&quot;https://www.droidcon.com/2021/11/22/supercharge-flutter-declarative-ui-with-code-generation/&quot;&gt;video&lt;/a&gt; for the ones who weren’t able to attend it!&lt;/p&gt;&lt;p&gt;Thank you to all the people I met and to the ones who attended my talk!&lt;/p&gt;&lt;p&gt;Let me know your thoughts!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Meet me at droidcon London 2021]]></title><description><![CDATA[I'm happy to say that I'm very excited to be speaking at  droidcon London  in a few days! I've been focused on Android since it was born and…]]></description><link>https://www.emanuelepapa.dev/meet-me-at-droidcon-london-2021/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/meet-me-at-droidcon-london-2021/</guid><category><![CDATA[Info]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Tue, 19 Oct 2021 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/ffa63d4f3654877751cda9841739b801/droidcon-london-2021.jpeg" length="41786" type="image/jpeg"/><content:encoded>&lt;p&gt;I’m happy to say that I’m very excited to be speaking at &lt;a href=&quot;https://www.london.droidcon.com/&quot;&gt;droidcon London&lt;/a&gt; in a few days!&lt;/p&gt;&lt;p&gt;I’ve been focused on Android since it was born and I’ll always be, but I also started experiencing multiplatform programming and so I wanted to share something I created for Flutter.&lt;br/&gt;
Actually, I’ve already exposed what’s the topic of my talk… I’m speaking about &lt;a href=&quot;/flutter-padding-widgets-generator-with-paddinger/&quot;&gt;Paddinger&lt;/a&gt;&lt;/p&gt;&lt;p&gt;You can find the &lt;a href=&quot;https://www.london.droidcon.com/program/supercharge-flutter-declarative-ui-with-code-generation&quot;&gt;talk abstract here&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Please like and retweet the &lt;a href=&quot;https://twitter.com/droidconLondon/status/1449374734718746626&quot;&gt;twitter post&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I’m really happy this is an in-person droidcon, so I can meet old friends and make new ones!&lt;/p&gt;&lt;p&gt;Come and say hi, I’ll be happy to meet you!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[New website! emanuelepapa.dev and w3ma.com merge]]></title><description><![CDATA[This is time for a big change! In the past there were 2 projects running side by side: emanuelepapa.dev , my resume website w3ma.com , my…]]></description><link>https://www.emanuelepapa.dev/new-website-emanuelepapa.dev-and-w3ma.com-merge/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/new-website-emanuelepapa.dev-and-w3ma.com-merge/</guid><category><![CDATA[Info]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Thu, 07 Oct 2021 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/ee919aefdc2cc00382d9fd3d87504e75/change-1245949_1280.jpeg" length="341681" type="image/jpeg"/><content:encoded>&lt;p&gt;This is time for a big change!&lt;/p&gt;&lt;p&gt;In the past there were 2 projects running side by side:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;emanuelepapa.dev&lt;/em&gt;, my resume website&lt;/li&gt;&lt;li&gt;&lt;em&gt;w3ma.com&lt;/em&gt;, my tech blog&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Right now, I decided to merge the 2 projects into one, hosted at &lt;em&gt;emanuelepapa.dev&lt;/em&gt;&lt;/p&gt;&lt;p&gt;I absolutely love the new look and I’m really happy with the final result!&lt;/p&gt;&lt;p&gt;Furthermore, &lt;a href=&quot;https://wordpress.org/&quot;&gt;Wordpress&lt;/a&gt; was abandoned in favour of &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;Gatsby&lt;/a&gt; and I’m happy with the new development/publish workflow.&lt;/p&gt;&lt;p&gt;I wish &lt;em&gt;emanuelepapa.dev&lt;/em&gt; a great success! 🚀&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Flutter Padding widgets generator with Paddinger]]></title><description><![CDATA[Paddinger  is a new package to help you deal with  Flutter  paddings and here you'll discover how! Have you ever been tired of writing…]]></description><link>https://www.emanuelepapa.dev/flutter-padding-widgets-generator-with-paddinger/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/flutter-padding-widgets-generator-with-paddinger/</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 16 May 2021 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/662e82433cd427861d6ea9d29eda818c/artur-shamsutdinov-Gll-v69L8iA-unsplash.jpeg" length="302653" type="image/jpeg"/><content:encoded>&lt;p&gt;&lt;em&gt;Paddinger&lt;/em&gt; is a new package to help you deal with &lt;em&gt;Flutter&lt;/em&gt; paddings and here you’ll discover how!&lt;/p&gt;&lt;p&gt;Have you ever been tired of writing &lt;em&gt;Padding&lt;/em&gt; and &lt;em&gt;EdgeInsets&lt;/em&gt; boilerplate everytime you need to add padding to a widget? I was, so I started looking for something to simplify this task. Furthermore, the necessary code to create declarative UIs can become very long and difficult to read, so, if there is a way to reduce it even by a few lines it’s something always worth to be considered. &lt;/p&gt;&lt;p&gt;I found out I was not the only one who was tired of this &lt;em&gt;Padding&lt;/em&gt; boilerplate because, searching for a solution, I found someone else struggling with the same problem. One of the solution I found was &lt;em&gt;Padder&lt;/em&gt;, which is available on &lt;a href=&quot;https://github.com/arklabsnz/Padder&quot;&gt;Github&lt;/a&gt; and on &lt;a href=&quot;https://pub.dev/packages/padder&quot;&gt;pub.dev&lt;/a&gt;. It’s a cool set of widgets which can help you dealing with paddings.&lt;/p&gt;&lt;p&gt;Unfortunately, it was not what I was looking for, but having found valid proofs that there was a problem affecting someone else other than me, I decided to build what I needed on my own, and so Paddinger was born.&lt;/p&gt;&lt;h2 id=&quot;what-is-paddinger&quot;&gt;What is Paddinger&lt;/h2&gt;&lt;p&gt;&lt;em&gt;Paddinger&lt;/em&gt; is a code generator for &lt;em&gt;Flutter&lt;/em&gt;: it builds &lt;em&gt;Padding&lt;/em&gt; widgets for you based on the values you have in your design system. An example is worth a thousand words, so here it is. Assuming you have&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;const double PADDING_NORMAL = 8;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;with &lt;em&gt;Paddinger&lt;/em&gt; you will obtain a set of widgets like&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;NormalAllPadding
NormalLeftPadding
NormalTopPadding
NormalRightPadding
NormalBottomPadding
NormalHorizontalPadding
NormalVerticalPadding
NormalLeftTopPadding
NormalLeftBottomPadding
NormalRightTopPadding
NormalRightBottomPadding
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;just by annotating with &lt;em&gt;@paddinger&lt;/em&gt; the constants you want the widgets to be generated for, like&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;@paddinger
const double PADDING_NORMAL = 8;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, you can use the generated widgets like every other one. Assuming you want to give a padding of 8 to a &lt;em&gt;Text&lt;/em&gt;, instead of writing&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;Padding(
  padding: const EdgeInsets.all(PADDING_NORMAL),
  child: Text(
    &amp;#x27;MyText&amp;#x27;,
  ),
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;you can just write&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;NormalAllPadding(
  child: Text(
    &amp;#x27;MyText&amp;#x27;,
  ),
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;which imho is far easier to remember, to write, to read and IDE code completion will come to rescue!&lt;/p&gt;&lt;h2 id=&quot;how-to-use-paddinger-in-your-project&quot;&gt;How to use Paddinger in your project&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;First of all, add &lt;em&gt;Paddinger&lt;/em&gt; as a dependency:&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;dependencies:
  paddinger_annotations: [latestVersionHere]

dev_dependencies:
  paddinger: [latestVersionHere]
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Create a file where you will add all your &lt;em&gt;PADDING_&lt;/em&gt; constants, the file could be named &lt;em&gt;paddings.dart&lt;/em&gt;.&lt;/li&gt;&lt;li&gt;Add your favourite &lt;em&gt;material&lt;/em&gt; or &lt;em&gt;cupertino&lt;/em&gt; import like:&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;// ignore: unused_import
import &amp;#x27;package:flutter/material.dart&amp;#x27;;
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Add the &lt;em&gt;part&lt;/em&gt; directive:&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-dart&quot;&gt;part &amp;#x27;paddings.g.dart&amp;#x27;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now run the code generation with &lt;code&gt;flutter pub run build_runner build --delete-conflicting-outputs&lt;/code&gt; and your widgets will be generated and ready to be used!&lt;/p&gt;&lt;p&gt;You can have a look at the example in the &lt;a href=&quot;https://github.com/ema987/paddinger&quot;&gt;Github repository&lt;/a&gt; or on &lt;a href=&quot;https://pub.dev/packages/paddinger/example&quot;&gt;pub.dev&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Happy Fluttering!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Clear all Android SharedPreferences]]></title><description><![CDATA[It may happen you would need to clear your all of your  SharedPreferences  without knowing in advance their keys. This can happen when you…]]></description><link>https://www.emanuelepapa.dev/clear-all-android-sharedpreferences/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/clear-all-android-sharedpreferences/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Tue, 29 Dec 2020 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/343f2c546f9c927ee0fa4df12eed0382/archive-1850170_1280.jpg" length="275750" type="image/jpg"/><content:encoded>&lt;p&gt;It may happen you would need to clear your all of your &lt;em&gt;SharedPreferences&lt;/em&gt; without knowing in advance their keys.&lt;/p&gt;&lt;p&gt;This can happen when you are writing tests: you don’t want your production code to publicly expose your &lt;em&gt;SharedPreferences keys&lt;/em&gt; neither you need a &lt;em&gt;clear()&lt;/em&gt; method, so you didn’t implement it. You may also need to clear third party &lt;em&gt;SharedPreferences&lt;/em&gt; to which you don’t have direct access.&lt;/p&gt;&lt;p&gt;Without changing your production code there is something you can do:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;access the app &lt;em&gt;SharedPreferences&lt;/em&gt; folder&lt;/li&gt;&lt;li&gt;get the &lt;em&gt;SharedPreferences Editor&lt;/em&gt; for each file&lt;/li&gt;&lt;li&gt;clear the &lt;em&gt;SharedPreferences&lt;/em&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;You can use the following code, called in a &lt;em&gt;@BeforeEach&lt;/em&gt; annotated method, to be sure each of your tests will run in a clean environment.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;private fun clearAllSharedPreferences(context: Context) {
    val sharedPreferencesPath = File(context.filesDir.parentFile!!.absolutePath + File.separator + &amp;quot;shared_prefs&amp;quot;)
    sharedPreferencesPath.listFiles()?.forEach { file -&amp;gt;
        context.getSharedPreferences(file.nameWithoutExtension, Context.MODE_PRIVATE).edit { clear() }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;be sure to include &lt;em&gt;androidx.core:core-ktx&lt;/em&gt; in your project to have that &lt;em&gt;edit()&lt;/em&gt; method&lt;/li&gt;&lt;li&gt;if you are running your tests using &lt;em&gt;Espresso&lt;/em&gt; you can access the app &lt;em&gt;Context&lt;/em&gt; using &lt;em&gt;InstrumentationRegistry.getInstrumentation().targetContext&lt;/em&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Happy coding!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How to debug an Annotation Processor in Android Studio]]></title><description><![CDATA[Writing an  Annotation Processor  in  Java/Kotlin  is a very interesting task and debugging comes very handy, but unfortunately it seems not…]]></description><link>https://www.emanuelepapa.dev/how-to-debug-an-annotation-processor-in-android-studio/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/how-to-debug-an-annotation-processor-in-android-studio/</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 05 Jul 2020 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/954ff5d2c00c8e4b062c4e7c86331867/ducks-in-a-row-2000x1500.jpeg" length="229100" type="image/jpeg"/><content:encoded>&lt;p&gt;Writing an &lt;em&gt;Annotation Processor&lt;/em&gt; in &lt;em&gt;Java/Kotlin&lt;/em&gt; is a very interesting task and debugging comes very handy, but unfortunately it seems not so easy to start a debug session.&lt;/p&gt;&lt;p&gt;The following information are valid for &lt;em&gt;Android Studio 3.6.3&lt;/em&gt; and &lt;em&gt;Kotlin 1.3.71&lt;/em&gt;. &lt;em&gt;Gradle&lt;/em&gt; is the build tool used for the project.&lt;/p&gt;&lt;p&gt;First thing to do is setup a new &lt;em&gt;Run/Debug Configuration&lt;/em&gt; in &lt;em&gt;Android Studio&lt;/em&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Choose &lt;em&gt;Run&lt;/em&gt; from the main menu&lt;/li&gt;&lt;li&gt;Choose &lt;em&gt;Edit Configurations&lt;/em&gt;&lt;/li&gt;&lt;li&gt;Now click on the + symbol in the top left corner to add a new Configuration&lt;/li&gt;&lt;li&gt;Choose &lt;em&gt;Remote&lt;/em&gt; from the list&lt;/li&gt;&lt;li&gt;Give it a friendly name (e.g. &lt;em&gt;AnnotationProcessorDebugger&lt;/em&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Setup your breakpoints wherever you need them and then run this command from the terminal:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;./gradlew --no-daemon -Dorg.gradle.debug=true :APP_MODULE_NAME:clean :APP_MODULE_NAME:compileDebugJavaWithJavac
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Replace &lt;em&gt;APP_MODULE_NAME&lt;/em&gt; with the name of the module having annotations which you want to debug. You will see the process will start and suddenly stop on &lt;em&gt;&amp;gt; Starting Daemon&lt;/em&gt; waiting for you to do something.&lt;/p&gt;&lt;p&gt;Now, click on the &lt;em&gt;Debug&lt;/em&gt; icon in the Toolbar (having selected the previously created &lt;em&gt;AnnotationProcessorDebugger&lt;/em&gt; configuration) and the debugger will hit your breakpoints!&lt;/p&gt;&lt;h2 id=&quot;additional-info&quot;&gt;Additional info&lt;/h2&gt;&lt;p&gt;The &lt;em&gt;clean&lt;/em&gt; task seems to be necessary.&lt;/p&gt;&lt;p&gt;Unfortunately, sometimes these steps are not enough, or simply don’t work, the debugger won’t hit your breakpoints. I see somebody else solved the issue after adding the following line to their &lt;em&gt;gradle.properties&lt;/em&gt; file.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;kapt.use.worker.api=true
&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title><![CDATA[Add UEFI support to your AMD HD7970 to use Secure Boot]]></title><description><![CDATA[Secure boot  is a security standard developed by members of the PC industry to help make sure that a device boots using only software that…]]></description><link>https://www.emanuelepapa.dev/add-uefi-support-to-your-amd-hd7970-to-use-secure-boot/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/add-uefi-support-to-your-amd-hd7970-to-use-secure-boot/</guid><category><![CDATA[Modding]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Tue, 31 Dec 2019 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/f6ba51e12b6ce9ad89df0276ec4fe5db/cristiano-firmani-tmTidmpILWw-unsplash-scaled.jpg" length="581378" type="image/jpg"/><content:encoded>&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-secure-boot&quot;&gt;Secure boot&lt;/a&gt; is a security standard developed by members of the PC industry to help make sure that a device boots using only software that is trusted by the Original Equipment Manufacturer (OEM). When the PC starts the installed software is being checked to see if its signature matches the one in the UEFI database and so is for the hardware: this case is about the BIOS of the Sapphire AMD HD7970 Vapor-X GHz Edition.&lt;/p&gt;&lt;p&gt;Its official BIOS doesn’t have UEFI support as GPU-Z shows:&lt;/p&gt;&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:381px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/017736db36cd50cf304740c6e9fdb0e6/ac471/sapphire_hd7970_015_022_000_001_000000_no_uefi.jpg&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:138.5964912280702%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAcABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAMBAgQF/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB7BAZjWFZU4sLD//EABsQAAICAwEAAAAAAAAAAAAAAAABEBECAxJB/9oACAEBAAEFAi6Md3UqPUWJCP/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABcQAAMBAAAAAAAAAAAAAAAAAAEQMDH/2gAIAQEABj8CWGX/xAAdEAADAAICAwAAAAAAAAAAAAAAARExQSFhcZGh/9oACAEBAAE/IXNGS6EpV7x7J9F4M2E7YEiLEMbPk//aAAwDAQACAAMAAAAQMNoM/8QAFhEAAwAAAAAAAAAAAAAAAAAAABAh/9oACAEDAQE/ECP/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAeEAEAAwABBQEAAAAAAAAAAAABABEhMVFhcYGhwf/aAAgBAQABPxDRxnWKuVgcOxcftA1fhf7MFdLnIrKch8n0ymgKLBNH2MwJcqhdyws4jgAMj//Z&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Starting situation with no UEFI support&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/017736db36cd50cf304740c6e9fdb0e6/ac471/sapphire_hd7970_015_022_000_001_000000_no_uefi.jpg&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/017736db36cd50cf304740c6e9fdb0e6/6c669/sapphire_hd7970_015_022_000_001_000000_no_uefi.jpg 285w,/static/017736db36cd50cf304740c6e9fdb0e6/ac471/sapphire_hd7970_015_022_000_001_000000_no_uefi.jpg 381w&quot; sizes=&quot;(max-width: 381px) 100vw, 381px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;Starting situation with no UEFI support&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Looking online I couldn’t find any official information about Sapphire making BIOS updates neither making an update to support UEFI. So I started looking for unofficial solutions…&lt;/p&gt;&lt;p&gt;On &lt;a href=&quot;https://www.techpowerup.com&quot;&gt;TechPowerUp&lt;/a&gt; I found a database of all the users’ uploaded GPU BIOS and so I started looking into the &lt;a href=&quot;https://www.techpowerup.com/vgabios/?architecture=&amp;amp;manufacturer=Sapphire&amp;amp;model=HD+7970&amp;amp;interface=&amp;amp;memType=&amp;amp;memSize=&amp;amp;since=&quot;&gt;Sapphire HD7970 list&lt;/a&gt;. Unfortunately, none of them was having UEFI support, but I saw there were versions more recent than mine! Then I started looking into other brand versions and I saw some Gigabyte BIOS having UEFI support and they should be official as they are available in the &lt;a href=&quot;https://www.gigabyte.com/Graphics-Card/GV-R797OC-3GD/support#support-dl-bios&quot;&gt;Gigabyte HD7970 support page&lt;/a&gt;. Nice work! Unfortunately, I’m not sure if it’s possible to flash a different brand BIOS even if the card model is the same, so I didn’t try this way.&lt;/p&gt;&lt;p&gt;I kept looking for unofficial solutions and eventually landed on a &lt;a href=&quot;https://www.overclock.net/forum/67-amd/1389206-do-you-want-uefi-gop-your-7950-7970-i-can-add-your-bios.html&quot;&gt;curious thread on overclock.net&lt;/a&gt; where a user says he is able to modify official BIOS to add UEFI support. After reading a lot of posts I found another &lt;a href=&quot;https://www.overclock.net/forum/67-amd/1389206-do-you-want-uefi-gop-your-7950-7970-i-can-add-your-bios.html&quot;&gt;interesting thread on Win-Raid.com&lt;/a&gt; where a user speaks about an automatic tool to add UEFI support to an original BIOS, whoa! No hex editing this time!&lt;/p&gt;&lt;p&gt;Want to read more about the GOP (Graphics Output Protocol) mentioned in that thread? Read it &lt;a href=&quot;https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface&quot;&gt;here on wikipedia&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;So, next section is about what I did to successfully modify and flash the new BIOS! &lt;em&gt;Do it at your own risk! It would be better if your VGA supports double BIOS so you can always revert your changes! Again, do it only if you know what you are doing and at your own risk, you could brick your card!&lt;/em&gt;&lt;/p&gt;&lt;p&gt;I downloaded the latest Sapphire HD7970 Vapor-X GHz Edition available on TechPowerUp which is version &lt;em&gt;015.037.000.000.000000&lt;/em&gt; and can be found &lt;a href=&quot;https://www.techpowerup.com/vgabios/151332/sapphire-hd7970-3072-130711&quot;&gt;here&lt;/a&gt;. I flashed it with &lt;a href=&quot;https://www.techpowerup.com/download/ati-atiflash/&quot;&gt;ATIFlash&lt;/a&gt; to be sure it worked, and here you can see the GPU-Z information.&lt;/p&gt;&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:380px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/c9ac8b0749f1e57f886b8ea2f99b706f/0246a/sapphire_hd7970_015_037_000_000_000000_no_uefi.jpg&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:138.94736842105263%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAcABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAMBAgQF/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB7BAZDaFZU4sLD//EABoQAQEAAgMAAAAAAAAAAAAAAAEAAxARMUH/2gAIAQEAAQUCuozjs16XMEX/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAYEAACAwAAAAAAAAAAAAAAAAAAARAwMv/aAAgBAQAGPwKMsdP/xAAeEAADAQAABwAAAAAAAAAAAAAAARExIUFRYXGRwf/aAAgBAQABPyFzkXqwW1e0Y+icDbCd2CRFiGNvk//aAAwDAQACAAMAAAAQcxoM/8QAFhEAAwAAAAAAAAAAAAAAAAAAABAh/9oACAEDAQE/ECP/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAdEAEAAwEAAgMAAAAAAAAAAAABABEhMUFRYYGR/9oACAEBAAE/EKFxnuXTdgcP2I0AauMY5fiVpWewT5nZSgLFgkD9jMCKKoXcsLOSigGR/9k=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Updated BIOS with no UEFI support&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/c9ac8b0749f1e57f886b8ea2f99b706f/0246a/sapphire_hd7970_015_037_000_000_000000_no_uefi.jpg&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/c9ac8b0749f1e57f886b8ea2f99b706f/6c669/sapphire_hd7970_015_037_000_000_000000_no_uefi.jpg 285w,/static/c9ac8b0749f1e57f886b8ea2f99b706f/0246a/sapphire_hd7970_015_037_000_000_000000_no_uefi.jpg 380w&quot; sizes=&quot;(max-width: 380px) 100vw, 380px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;Updated BIOS with no UEFI support&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Then, I used the GOP Update tool to add UEFI support to this BIOS. The procedure worked flawlessly as shown by tool output here&lt;/p&gt;&lt;pre&gt;&lt;code&gt;************************* GOPupd 1.9.6.5 *************************


************************ Update EFI GOP ************************


***************** Drop VBIOS file on this .bat *****************


File Not Found
The system cannot find the file specified.
Dumping info from = official_037.rom


ID of ROM file    = 1002-6798

No EFI ROM found!

No EFI ROM found or error on decompression !!!

***************************************************************
***                Extracting with GOPupd...                ***
***************************************************************

---------------------------------------------------------------

***************************************************************
***                Processing with GOPupd...                ***
***************************************************************

GOP is not present!!!


Do you want to update GOP to latest available? Y for yes or N for no: y

  Fixing last-image-bit in PCI Structure of Legacy ROM!

  Using AMD byte for checksum!

  Fixing ID for EFI image. No checksum correction is needed.

  Removing unnecessary end padding.


File &amp;quot;official_037_updGOP.rom&amp;quot; with updated GOP 1.67.0.15.50 was written!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, the new &lt;em&gt;official_037_updGOP.rom&lt;/em&gt; BIOS was flashed with ATIFlash and GPU-Z showed UEFI support! Tada!&lt;/p&gt;&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:381px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/a536121dd2716d0fe0d065b23a9260a9/ac471/sapphire_hd7970_015_037_000_000_000000_yes_uefi.jpg&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:138.94736842105263%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAcABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAECAwQF/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB7AgyG0Iuq4ZAP//EABkQAAIDAQAAAAAAAAAAAAAAAAABAxARQf/aAAgBAQABBQI3BTp2q6jRCP/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABcQAAMBAAAAAAAAAAAAAAAAAAEQMDL/2gAIAQEABj8CWTL/xAAdEAACAgIDAQAAAAAAAAAAAAAAARExQWEhcZHB/9oACAEBAAE/IXGDZQtleo8n0TguYI2oSETETGvs/9oADAMBAAIAAwAAABBzGjD/xAAWEQADAAAAAAAAAAAAAAAAAAAAECH/2gAIAQMBAT8QI//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8QH//EABwQAQEBAQACAwAAAAAAAAAAAAERACExUUGBkf/aAAgBAQABPxCC0c96krQPB+5EgDLnHPS6dJz2DfFvnRQFFwkD9juBllULddp4ykAQz//Z&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Updated BIOS with UEFI support&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/a536121dd2716d0fe0d065b23a9260a9/ac471/sapphire_hd7970_015_037_000_000_000000_yes_uefi.jpg&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/a536121dd2716d0fe0d065b23a9260a9/6c669/sapphire_hd7970_015_037_000_000_000000_yes_uefi.jpg 285w,/static/a536121dd2716d0fe0d065b23a9260a9/ac471/sapphire_hd7970_015_037_000_000_000000_yes_uefi.jpg 381w&quot; sizes=&quot;(max-width: 381px) 100vw, 381px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;Updated BIOS with UEFI support&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;&lt;p&gt;I found no issues at all with the modified BIOS and I was also able to disable CSM (Compatibility Support Module, read more &lt;a href=&quot;https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface&quot;&gt;here&lt;/a&gt;) from my motherboard BIOS which didn’t let me do it before due to the VGA lack of UEFI support!&lt;/p&gt;&lt;p&gt;If you have a different AMD VGA (or NVIDIA one, check the supported ones) you might be able to follow the same steps to modify your official BIOS. If you have the same card I have, I’ve uploaded the firmware on TechPowerUp so you can &lt;a href=&quot;https://www.techpowerup.com/vgabios/216690/216690&quot;&gt;download it here&lt;/a&gt;. &lt;/p&gt;</content:encoded></item><item><title><![CDATA[Sleep automation with Mi Band and Home Assistant]]></title><description><![CDATA[Have you ever thought about using your Mi Band to help you automate stuff? Read this article and you will know how you can use your Mi Band…]]></description><link>https://www.emanuelepapa.dev/sleep-automation-with-mi-band-and-home-assistant/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/sleep-automation-with-mi-band-and-home-assistant/</guid><category><![CDATA[Smart Home]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sat, 23 Nov 2019 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/2a7539f55319196afc001182eddc671b/miband2-2538699_1920.jpg" length="87223" type="image/jpg"/><content:encoded>&lt;p&gt;Have you ever thought about using your Mi Band to help you automate stuff? Read this article and you will know how you can use your Mi Band sleep detection to trigger automations in your Home Assistant!&lt;/p&gt;&lt;p&gt;You will need:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.mc.miband1&quot;&gt;Tasker&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.mc.miband1&quot;&gt;Notify &amp;amp; Fitness for Mi Band&lt;/a&gt;&lt;/li&gt;&lt;li&gt;A Mi Band &lt;/li&gt;&lt;li&gt;An automation which creates a webhook and then publishes your sleep status on a MQTT topic&lt;/li&gt;&lt;li&gt;A sensor based on the MQTT topic previously defined (it can be avoided for the purpose of this automation but I prefer to have it to keep track of the sleep status) &lt;/li&gt;&lt;li&gt;Tasker profiles which react to your sleep status&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Create a &lt;em&gt;sleep_status.yaml&lt;/em&gt; file in your packages directory with the following content.&lt;/p&gt;&lt;p&gt;Replace &lt;em&gt;[YOUR_NAME]&lt;/em&gt; and &lt;em&gt;[YOUR_WEBHOOK_ID]&lt;/em&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;homeassistant:

automation:
  - alias: [YOUR_NAME] Sleep Status
    initial_state: true
    trigger:
      - platform: webhook
      webhook_id: [YOUR_WEBHOOK_ID]
      condition: []
    action:
      - service: mqtt.publish
        data_template: 
          payload: &amp;quot;{\&amp;quot;sleepStatus\&amp;quot;: \&amp;quot;{{trigger.json.sleepStatus}}\&amp;quot;}&amp;quot;
          topic: &amp;quot;sleepstatus/[YOUR_NAME]&amp;quot;

sensor:
  - platform: mqtt
    name: &amp;quot;[YOUR_NAME] Sleep Status&amp;quot;
    state_topic: &amp;quot;sleepstatus/[YOUR_NAME]&amp;quot;
    value_template: &amp;#x27;{{ value_json[&amp;quot;sleepStatus&amp;quot;] }}&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Take now your phone, open Tasker and setup the following profiles which will send your sleep status events (when you wake up and fall asleep) to your Home Assistant.&lt;/p&gt;&lt;p&gt;Replace &lt;em&gt;[YOUR_HOME_ASSISTANT_DOMAIN_NAME]&lt;/em&gt; and &lt;em&gt;[YOUR_WEBHOOK_ID]&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;TaskerData sr=&amp;quot;&amp;quot; dvi=&amp;quot;1&amp;quot; tv=&amp;quot;5.9.rc&amp;quot;&amp;gt;
    &amp;lt;Profile sr=&amp;quot;prof61&amp;quot; ve=&amp;quot;2&amp;quot;&amp;gt;
        &amp;lt;cdate&amp;gt;1574199690392&amp;lt;/cdate&amp;gt;
        &amp;lt;clp&amp;gt;true&amp;lt;/clp&amp;gt;
        &amp;lt;edate&amp;gt;1574529186304&amp;lt;/edate&amp;gt;
        &amp;lt;flags&amp;gt;8&amp;lt;/flags&amp;gt;
        &amp;lt;id&amp;gt;61&amp;lt;/id&amp;gt;
        &amp;lt;mid0&amp;gt;63&amp;lt;/mid0&amp;gt;
        &amp;lt;nme&amp;gt;MiBand Fell Asleep&amp;lt;/nme&amp;gt;
        &amp;lt;Event sr=&amp;quot;con0&amp;quot; ve=&amp;quot;2&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;599&amp;lt;/code&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;com.mc.miband.tasker.fellAsleep&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg1&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg2&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg3&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg4&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
        &amp;lt;/Event&amp;gt;
    &amp;lt;/Profile&amp;gt;
    &amp;lt;Task sr=&amp;quot;task63&amp;quot;&amp;gt;
        &amp;lt;cdate&amp;gt;1574199712492&amp;lt;/cdate&amp;gt;
        &amp;lt;edate&amp;gt;1574529096550&amp;lt;/edate&amp;gt;
        &amp;lt;id&amp;gt;63&amp;lt;/id&amp;gt;
        &amp;lt;nme&amp;gt;Send Asleep Payload&amp;lt;/nme&amp;gt;
        &amp;lt;pri&amp;gt;6&amp;lt;/pri&amp;gt;
        &amp;lt;Action sr=&amp;quot;act0&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;339&amp;lt;/code&amp;gt;
            &amp;lt;Bundle sr=&amp;quot;arg0&amp;quot;&amp;gt;
                &amp;lt;Vals sr=&amp;quot;val&amp;quot;&amp;gt;
                    &amp;lt;net.dinglisch.android.tasker.RELEVANT_VARIABLES&amp;gt;
                        &amp;lt;StringArray sr=&amp;quot;&amp;quot;&amp;gt;
                            &amp;lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0&amp;gt;%http_data
Data
Data that the server responded from the HTTP request.&amp;lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0&amp;gt;
                            &amp;lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1&amp;gt;%http_file_output
File Output
Will always contain the file&amp;#x27;s full path even if you specified a directory as the File to save.&amp;lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1&amp;gt;
                            &amp;lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2&amp;gt;%http_response_code
Response Code
The HTTP Code the server responded&amp;lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2&amp;gt;
                            &amp;lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3&amp;gt;%http_headers()
Response Headers
The HTTP Headers the server sent in the response. Each header is in the &amp;#x27;key:value&amp;#x27; format&amp;lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3&amp;gt;
                            &amp;lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4&amp;gt;%http_response_length
Response Length
The size of the response in bytes&amp;lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4&amp;gt;
                        &amp;lt;/StringArray&amp;gt;
                    &amp;lt;/net.dinglisch.android.tasker.RELEVANT_VARIABLES&amp;gt;
                    &amp;lt;net.dinglisch.android.tasker.RELEVANT_VARIABLES-type&amp;gt;[Ljava.lang.String;&amp;lt;/net.dinglisch.android.tasker.RELEVANT_VARIABLES-type&amp;gt;
                &amp;lt;/Vals&amp;gt;
            &amp;lt;/Bundle&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg1&amp;quot; val=&amp;quot;1&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg2&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;https://[YOUR_HOME_ASSISTANT_DOMAIN_NAME]/api/webhook/[YOUR_WEBHOOK_ID]y&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg3&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;Content-Type:application/json&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg4&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg5&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;{ &amp;quot;sleepStatus&amp;quot; : &amp;quot;Asleep&amp;quot; }&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg6&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg7&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg8&amp;quot; val=&amp;quot;30&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg9&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
        &amp;lt;/Action&amp;gt;
    &amp;lt;/Task&amp;gt;
&amp;lt;/TaskerData&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;TaskerData sr=&amp;quot;&amp;quot; dvi=&amp;quot;1&amp;quot; tv=&amp;quot;5.9.rc&amp;quot;&amp;gt;
    &amp;lt;Profile sr=&amp;quot;prof66&amp;quot; ve=&amp;quot;2&amp;quot;&amp;gt;
        &amp;lt;cdate&amp;gt;1574200024388&amp;lt;/cdate&amp;gt;
        &amp;lt;edate&amp;gt;1574529166065&amp;lt;/edate&amp;gt;
        &amp;lt;flags&amp;gt;8&amp;lt;/flags&amp;gt;
        &amp;lt;id&amp;gt;66&amp;lt;/id&amp;gt;
        &amp;lt;mid0&amp;gt;65&amp;lt;/mid0&amp;gt;
        &amp;lt;nme&amp;gt;MiBand WokeUp&amp;lt;/nme&amp;gt;
        &amp;lt;Event sr=&amp;quot;con0&amp;quot; ve=&amp;quot;2&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;599&amp;lt;/code&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;com.mc.miband.tasker.wokeUp&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg1&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg2&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg3&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg4&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
        &amp;lt;/Event&amp;gt;
    &amp;lt;/Profile&amp;gt;
    &amp;lt;Task sr=&amp;quot;task65&amp;quot;&amp;gt;
        &amp;lt;cdate&amp;gt;1574199886906&amp;lt;/cdate&amp;gt;
        &amp;lt;edate&amp;gt;1574529090100&amp;lt;/edate&amp;gt;
        &amp;lt;id&amp;gt;65&amp;lt;/id&amp;gt;
        &amp;lt;nme&amp;gt;Send Awake Payload&amp;lt;/nme&amp;gt;
        &amp;lt;pri&amp;gt;6&amp;lt;/pri&amp;gt;
        &amp;lt;Action sr=&amp;quot;act0&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;339&amp;lt;/code&amp;gt;
            &amp;lt;Bundle sr=&amp;quot;arg0&amp;quot;&amp;gt;
                &amp;lt;Vals sr=&amp;quot;val&amp;quot;&amp;gt;
                    &amp;lt;net.dinglisch.android.tasker.RELEVANT_VARIABLES&amp;gt;
                        &amp;lt;StringArray sr=&amp;quot;&amp;quot;&amp;gt;
                            &amp;lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0&amp;gt;%http_data
Data
Data that the server responded from the HTTP request.&amp;lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0&amp;gt;
                            &amp;lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1&amp;gt;%http_file_output
File Output
Will always contain the file&amp;#x27;s full path even if you specified a directory as the File to save.&amp;lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1&amp;gt;
                            &amp;lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2&amp;gt;%http_response_code
Response Code
The HTTP Code the server responded&amp;lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2&amp;gt;
                            &amp;lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3&amp;gt;%http_headers()
Response Headers
The HTTP Headers the server sent in the response. Each header is in the &amp;#x27;key:value&amp;#x27; format&amp;lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3&amp;gt;
                            &amp;lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4&amp;gt;%http_response_length
Response Length
The size of the response in bytes&amp;lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4&amp;gt;
                        &amp;lt;/StringArray&amp;gt;
                    &amp;lt;/net.dinglisch.android.tasker.RELEVANT_VARIABLES&amp;gt;
                    &amp;lt;net.dinglisch.android.tasker.RELEVANT_VARIABLES-type&amp;gt;[Ljava.lang.String;&amp;lt;/net.dinglisch.android.tasker.RELEVANT_VARIABLES-type&amp;gt;
                &amp;lt;/Vals&amp;gt;
            &amp;lt;/Bundle&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg1&amp;quot; val=&amp;quot;1&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg2&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;https://[YOUR_HOME_ASSISTANT_DOMAIN_NAME]/api/webhook/[YOUR_WEBHOOK_ID]&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg3&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;Content-Type:application/json&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg4&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg5&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;{ &amp;quot;sleepStatus&amp;quot; : &amp;quot;Awake&amp;quot; }&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg6&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg7&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg8&amp;quot; val=&amp;quot;30&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg9&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
        &amp;lt;/Action&amp;gt;
    &amp;lt;/Task&amp;gt;
&amp;lt;/TaskerData&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, you can create a Node-RED flow which reacts to &lt;em&gt;sensor.[YOUR_NAME]_sleep_status&lt;/em&gt; with &lt;em&gt;Awake/Asleep&lt;/em&gt; value and executes your preferred stuff (e.g. turn off all the lights as soon as you fall asleep).&lt;/p&gt;&lt;p&gt;I have used my Mi Band 2 and it detects my sleep activity usually within 5 minutes so don’t use it for real-time automations. &lt;/p&gt;&lt;p&gt;Edit: it works on my Mi Band 4, too!&lt;/p&gt;&lt;p&gt;Let me know what automations you are going to create!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Light control using an Aqara Wireless Remote Switch with Home Assistant and Node-RED]]></title><description><![CDATA[Home automation is nice, sending commands with your voice is very cool, but sometimes, especially at night, you might want to just use…]]></description><link>https://www.emanuelepapa.dev/light-control-using-an-aqara-wireless-remote-switch-with-home-assistant-and-node-red/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/light-control-using-an-aqara-wireless-remote-switch-with-home-assistant-and-node-red/</guid><category><![CDATA[Smart Home]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Fri, 04 Oct 2019 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/837d07ea0e07f80a45be3470c82385bc/smarthome-4447519_1920.jpeg" length="191305" type="image/jpeg"/><content:encoded>&lt;p&gt;Home automation is nice, sending commands with your voice is very cool, but sometimes, especially at night, you might want to just use traditional methods like a wall switch. You can do it very easily without compromising your cool voice controls, and you can also customize the way it works to suit your needs!&lt;/p&gt;&lt;p&gt;Let’s start, this is what you need:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; running wherever you like&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/hassio-addons/addon-node-red&quot;&gt;Node-RED&lt;/a&gt; configured on your Home Assistant&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/danielwelch/hassio-zigbee2mqtt&quot;&gt;Zigbee2mqtt&lt;/a&gt; configured on your Home Assistant&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.aqara.com/en/wireless_remote_switch.html&quot;&gt;Aqara Wireless Remote Switch&lt;/a&gt; paired with Zigbee2mqtt&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The &lt;em&gt;Aqara Wireless Remote Switch&lt;/em&gt; is a switch you can place anywhere because it doesn’t need any wire, it works with a small battery which will last a very long time. To make the following configuration you need to use the double rocker one: it has a lot of different click interactions you can customize.&lt;/p&gt;&lt;p&gt;This is the configuration you can have using the &lt;em&gt;Node-RED&lt;/em&gt; flow shown in this post:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;Left click&lt;/em&gt;: turn on/off the lights (toggle)&lt;/li&gt;&lt;li&gt;&lt;em&gt;Right click&lt;/em&gt;: switch between a predefined set of colors&lt;/li&gt;&lt;li&gt;&lt;em&gt;Left long click&lt;/em&gt;: decrease brightness&lt;/li&gt;&lt;li&gt;&lt;em&gt;Right long click&lt;/em&gt;: increase brightness&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This &lt;em&gt;Node-RED&lt;/em&gt; flow should be pretty straightforward, anyway, let’s have some more details.&lt;/p&gt;&lt;p&gt;The first node is an event state node that reacts to the changes of the &lt;em&gt;Aqara Switch&lt;/em&gt;: it can have different values like &lt;em&gt;left&lt;/em&gt;, &lt;em&gt;right&lt;/em&gt;, &lt;em&gt;left_long&lt;/em&gt;, &lt;em&gt;right_long&lt;/em&gt;, etc. &lt;em&gt;Home Assistant&lt;/em&gt; detects it as a sensor.&lt;/p&gt;&lt;p&gt;The second node is a switch used to take different actions based on the type of click done on the &lt;em&gt;Aqara switch&lt;/em&gt;.&lt;/p&gt;&lt;h2 id=&quot;left-click-scenario&quot;&gt;Left click scenario&lt;/h2&gt;&lt;p&gt;A service call to toggle the lights, very easy!&lt;/p&gt;&lt;h2 id=&quot;right-click-scenario&quot;&gt;Right click scenario&lt;/h2&gt;&lt;p&gt;This is the most interesting part, the state machine node! It is used to create a finite state machine of the colors and transitions you want the lights to have. Unfortunately, I couldn’t find a way to make the state machine move to the next state based only on the current one, without taking care of the input value, so I managed to achieve it with a workaround! &lt;/p&gt;&lt;p&gt;After the state machine node there is a switch node which checks if the state machine already did its transition checking a value which is put into the payload object in the following function node. This function node also put the current state machine value as payload of the message, so the state machine can correctly move. In doing so, the second time the flow will go to another function node which generates the light settings based on the value the state machine now has. Then, the last node simply sets this configuration to the lights.&lt;/p&gt;&lt;h2 id=&quot;left-long-click-scenario&quot;&gt;Left long click scenario&lt;/h2&gt;&lt;p&gt;A current state node which gets the current brightness of the light followed by a function node which calculates the increased brightness value. In the function node you can customize the brightness interval and the minimum brightness you would like to reach. The last node is a service call which sets the new brightness to the light.&lt;/p&gt;&lt;h2 id=&quot;right-long-click-scenario&quot;&gt;Right long click scenario&lt;/h2&gt;&lt;p&gt;Same as the left long click scenario but this time the light brightness is increased, so the function node calculates the increased value the light will have. Customization for brightness interval and maximum brightness are available here as well.&lt;/p&gt;&lt;p&gt;Enjoy your wireless switch!&lt;/p&gt;&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1140px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/2883e2430dea179d31ffa2a4c08efe48/e6f81/smart_switch_node_red_flow.jpg&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:22.105263157894736%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAEABQDASIAAhEBAxEB/8QAFwABAAMAAAAAAAAAAAAAAAAAAAECBf/EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAHbgC4b/8QAGBAAAgMAAAAAAAAAAAAAAAAAAAECESH/2gAIAQEAAQUCksKP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFBABAAAAAAAAAAAAAAAAAAAAEP/aAAgBAQAGPwJ//8QAGBABAQEBAQAAAAAAAAAAAAAAAQARQVH/2gAIAQEAAT8h9LBvWBl//9oADAMBAAIAAwAAABB8P//EABcRAQEBAQAAAAAAAAAAAAAAAAEAMXH/2gAIAQMBAT8QNeQX/8QAFxEBAAMAAAAAAAAAAAAAAAAAARAxcf/aAAgBAgEBPxBo2P/EABgQAQEBAQEAAAAAAAAAAAAAAAERMQBx/9oACAEBAAE/EJQaGt5ADld7Yq+vf//Z&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Wireless Switch flow&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/2883e2430dea179d31ffa2a4c08efe48/0470c/smart_switch_node_red_flow.jpg&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/2883e2430dea179d31ffa2a4c08efe48/6c669/smart_switch_node_red_flow.jpg 285w,/static/2883e2430dea179d31ffa2a4c08efe48/882a4/smart_switch_node_red_flow.jpg 570w,/static/2883e2430dea179d31ffa2a4c08efe48/0470c/smart_switch_node_red_flow.jpg 1140w,/static/2883e2430dea179d31ffa2a4c08efe48/e6f81/smart_switch_node_red_flow.jpg 1443w&quot; sizes=&quot;(max-width: 1140px) 100vw, 1140px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;Wireless Switch flow&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
   {
      &amp;quot;id&amp;quot;:&amp;quot;63038ca5.043d84&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;server-state-changed&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;SmartSwitch1&amp;quot;,
      &amp;quot;server&amp;quot;:&amp;quot;b0807fb3.bda6d&amp;quot;,
      &amp;quot;version&amp;quot;:1,
      &amp;quot;entityidfilter&amp;quot;:&amp;quot;sensor.smartswitch1_click&amp;quot;,
      &amp;quot;entityidfiltertype&amp;quot;:&amp;quot;exact&amp;quot;,
      &amp;quot;outputinitially&amp;quot;:false,
      &amp;quot;state_type&amp;quot;:&amp;quot;str&amp;quot;,
      &amp;quot;haltifstate&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;halt_if_type&amp;quot;:&amp;quot;str&amp;quot;,
      &amp;quot;halt_if_compare&amp;quot;:&amp;quot;is&amp;quot;,
      &amp;quot;outputs&amp;quot;:1,
      &amp;quot;output_only_on_state_change&amp;quot;:true,
      &amp;quot;x&amp;quot;:110,
      &amp;quot;y&amp;quot;:160,
      &amp;quot;wires&amp;quot;:[
         [
            &amp;quot;e4f033f6.02139&amp;quot;
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;e4f033f6.02139&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;switch&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;click is&amp;quot;,
      &amp;quot;property&amp;quot;:&amp;quot;payload&amp;quot;,
      &amp;quot;propertyType&amp;quot;:&amp;quot;msg&amp;quot;,
      &amp;quot;rules&amp;quot;:[
         {
            &amp;quot;t&amp;quot;:&amp;quot;eq&amp;quot;,
            &amp;quot;v&amp;quot;:&amp;quot;left&amp;quot;,
            &amp;quot;vt&amp;quot;:&amp;quot;str&amp;quot;
         },
         {
            &amp;quot;t&amp;quot;:&amp;quot;eq&amp;quot;,
            &amp;quot;v&amp;quot;:&amp;quot;right&amp;quot;,
            &amp;quot;vt&amp;quot;:&amp;quot;str&amp;quot;
         },
         {
            &amp;quot;t&amp;quot;:&amp;quot;eq&amp;quot;,
            &amp;quot;v&amp;quot;:&amp;quot;left_long&amp;quot;,
            &amp;quot;vt&amp;quot;:&amp;quot;str&amp;quot;
         },
         {
            &amp;quot;t&amp;quot;:&amp;quot;eq&amp;quot;,
            &amp;quot;v&amp;quot;:&amp;quot;right_long&amp;quot;,
            &amp;quot;vt&amp;quot;:&amp;quot;str&amp;quot;
         }
      ],
      &amp;quot;checkall&amp;quot;:&amp;quot;true&amp;quot;,
      &amp;quot;repair&amp;quot;:false,
      &amp;quot;outputs&amp;quot;:4,
      &amp;quot;x&amp;quot;:290,
      &amp;quot;y&amp;quot;:160,
      &amp;quot;wires&amp;quot;:[
         [
            &amp;quot;c92d98e8.8de858&amp;quot;
         ],
         [
            &amp;quot;bbcdb78.3712348&amp;quot;
         ],
         [
            &amp;quot;752f2116.e5b66&amp;quot;
         ],
         [
            &amp;quot;a10c6932.4ae3a8&amp;quot;
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;c92d98e8.8de858&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;api-call-service&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;toggle living room lights&amp;quot;,
      &amp;quot;server&amp;quot;:&amp;quot;b0807fb3.bda6d&amp;quot;,
      &amp;quot;version&amp;quot;:1,
      &amp;quot;service_domain&amp;quot;:&amp;quot;light&amp;quot;,
      &amp;quot;service&amp;quot;:&amp;quot;toggle&amp;quot;,
      &amp;quot;entityId&amp;quot;:&amp;quot;group.living_room_lights&amp;quot;,
      &amp;quot;data&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;dataType&amp;quot;:&amp;quot;json&amp;quot;,
      &amp;quot;mergecontext&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;output_location&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;output_location_type&amp;quot;:&amp;quot;none&amp;quot;,
      &amp;quot;mustacheAltTags&amp;quot;:false,
      &amp;quot;x&amp;quot;:530,
      &amp;quot;y&amp;quot;:60,
      &amp;quot;wires&amp;quot;:[
         [
            
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;515a6b84.e21c54&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;api-call-service&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;set brightness to living room lights&amp;quot;,
      &amp;quot;server&amp;quot;:&amp;quot;b0807fb3.bda6d&amp;quot;,
      &amp;quot;version&amp;quot;:1,
      &amp;quot;service_domain&amp;quot;:&amp;quot;light&amp;quot;,
      &amp;quot;service&amp;quot;:&amp;quot;turn_on&amp;quot;,
      &amp;quot;entityId&amp;quot;:&amp;quot;group.living_room_lights&amp;quot;,
      &amp;quot;data&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;dataType&amp;quot;:&amp;quot;json&amp;quot;,
      &amp;quot;mergecontext&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;output_location&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;output_location_type&amp;quot;:&amp;quot;none&amp;quot;,
      &amp;quot;mustacheAltTags&amp;quot;:false,
      &amp;quot;x&amp;quot;:1200,
      &amp;quot;y&amp;quot;:220,
      &amp;quot;wires&amp;quot;:[
         [
            
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;752f2116.e5b66&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;api-current-state&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;server&amp;quot;:&amp;quot;b0807fb3.bda6d&amp;quot;,
      &amp;quot;version&amp;quot;:1,
      &amp;quot;outputs&amp;quot;:1,
      &amp;quot;halt_if&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;halt_if_type&amp;quot;:&amp;quot;str&amp;quot;,
      &amp;quot;halt_if_compare&amp;quot;:&amp;quot;is&amp;quot;,
      &amp;quot;override_topic&amp;quot;:false,
      &amp;quot;entity_id&amp;quot;:&amp;quot;light.left_living_room&amp;quot;,
      &amp;quot;state_type&amp;quot;:&amp;quot;str&amp;quot;,
      &amp;quot;state_location&amp;quot;:&amp;quot;data&amp;quot;,
      &amp;quot;override_payload&amp;quot;:&amp;quot;msg&amp;quot;,
      &amp;quot;entity_location&amp;quot;:&amp;quot;data&amp;quot;,
      &amp;quot;override_data&amp;quot;:&amp;quot;msg&amp;quot;,
      &amp;quot;blockInputOverrides&amp;quot;:false,
      &amp;quot;x&amp;quot;:560,
      &amp;quot;y&amp;quot;:200,
      &amp;quot;wires&amp;quot;:[
         [
            &amp;quot;91a6b4b5.c19558&amp;quot;
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;ba574398.cc6a&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;function&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;calculate increased brightness&amp;quot;,
      &amp;quot;func&amp;quot;:&amp;quot;const BRIGHTNESS_INTERVAL = 100;\nconst BRIGHTNESS_MAX = 255;\n\nlet brightness = msg.data.attributes.brightness;\nlet entityId = msg.data.entity_id;\nbrightness = brightness + BRIGHTNESS_INTERVAL\nif (brightness &amp;gt; BRIGHTNESS_MAX) {\n    brightness = BRIGHTNESS_MAX\n}\nmsg.payload = {\n    \&amp;quot;data\&amp;quot;: {\n        \&amp;quot;brightness\&amp;quot;:brightness\n        \n    }\n}\nreturn msg;&amp;quot;,
      &amp;quot;outputs&amp;quot;:1,
      &amp;quot;noerr&amp;quot;:0,
      &amp;quot;x&amp;quot;:870,
      &amp;quot;y&amp;quot;:260,
      &amp;quot;wires&amp;quot;:[
         [
            &amp;quot;515a6b84.e21c54&amp;quot;
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;91a6b4b5.c19558&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;function&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;calculate decreased brightness&amp;quot;,
      &amp;quot;func&amp;quot;:&amp;quot;const BRIGHTNESS_INTERVAL = 100;\nconst BRIGHTNESS_MIN = 1;\n\nlet brightness = msg.data.attributes.brightness;\nlet entityId = msg.data.entity_id;\nbrightness = brightness - BRIGHTNESS_INTERVAL\nif (brightness &amp;lt; BRIGHTNESS_MIN) {\n    brightness = BRIGHTNESS_MIN\n}\nmsg.payload = {\n    \&amp;quot;data\&amp;quot;: {\n        \&amp;quot;brightness\&amp;quot;:brightness\n        \n    }\n}\nreturn msg;&amp;quot;,
      &amp;quot;outputs&amp;quot;:1,
      &amp;quot;noerr&amp;quot;:0,
      &amp;quot;x&amp;quot;:870,
      &amp;quot;y&amp;quot;:200,
      &amp;quot;wires&amp;quot;:[
         [
            &amp;quot;515a6b84.e21c54&amp;quot;
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;a10c6932.4ae3a8&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;api-current-state&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;server&amp;quot;:&amp;quot;b0807fb3.bda6d&amp;quot;,
      &amp;quot;version&amp;quot;:1,
      &amp;quot;outputs&amp;quot;:1,
      &amp;quot;halt_if&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;halt_if_type&amp;quot;:&amp;quot;str&amp;quot;,
      &amp;quot;halt_if_compare&amp;quot;:&amp;quot;is&amp;quot;,
      &amp;quot;override_topic&amp;quot;:false,
      &amp;quot;entity_id&amp;quot;:&amp;quot;light.left_living_room&amp;quot;,
      &amp;quot;state_type&amp;quot;:&amp;quot;str&amp;quot;,
      &amp;quot;state_location&amp;quot;:&amp;quot;data&amp;quot;,
      &amp;quot;override_payload&amp;quot;:&amp;quot;msg&amp;quot;,
      &amp;quot;entity_location&amp;quot;:&amp;quot;data&amp;quot;,
      &amp;quot;override_data&amp;quot;:&amp;quot;msg&amp;quot;,
      &amp;quot;blockInputOverrides&amp;quot;:false,
      &amp;quot;x&amp;quot;:560,
      &amp;quot;y&amp;quot;:260,
      &amp;quot;wires&amp;quot;:[
         [
            &amp;quot;ba574398.cc6a&amp;quot;
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;bbcdb78.3712348&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;state-machine&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;bulb color state&amp;quot;,
      &amp;quot;triggerProperty&amp;quot;:&amp;quot;payload&amp;quot;,
      &amp;quot;triggerPropertyType&amp;quot;:&amp;quot;msg&amp;quot;,
      &amp;quot;stateProperty&amp;quot;:&amp;quot;topic&amp;quot;,
      &amp;quot;statePropertyType&amp;quot;:&amp;quot;msg&amp;quot;,
      &amp;quot;outputStateChangeOnly&amp;quot;:false,
      &amp;quot;throwException&amp;quot;:false,
      &amp;quot;states&amp;quot;:[
         &amp;quot;white&amp;quot;,
         &amp;quot;yellow&amp;quot;
      ],
      &amp;quot;transitions&amp;quot;:[
         {
            &amp;quot;name&amp;quot;:&amp;quot;white&amp;quot;,
            &amp;quot;from&amp;quot;:&amp;quot;white&amp;quot;,
            &amp;quot;to&amp;quot;:&amp;quot;yellow&amp;quot;
         },
         {
            &amp;quot;name&amp;quot;:&amp;quot;yellow&amp;quot;,
            &amp;quot;from&amp;quot;:&amp;quot;yellow&amp;quot;,
            &amp;quot;to&amp;quot;:&amp;quot;white&amp;quot;
         }
      ],
      &amp;quot;x&amp;quot;:540,
      &amp;quot;y&amp;quot;:140,
      &amp;quot;wires&amp;quot;:[
         [
            &amp;quot;10c6a2fd.57b7dd&amp;quot;
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;f1efc70d.5de9e8&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;api-call-service&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;set color to living room lights&amp;quot;,
      &amp;quot;server&amp;quot;:&amp;quot;b0807fb3.bda6d&amp;quot;,
      &amp;quot;version&amp;quot;:1,
      &amp;quot;service_domain&amp;quot;:&amp;quot;light&amp;quot;,
      &amp;quot;service&amp;quot;:&amp;quot;turn_on&amp;quot;,
      &amp;quot;entityId&amp;quot;:&amp;quot;group.living_room_lights&amp;quot;,
      &amp;quot;data&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;dataType&amp;quot;:&amp;quot;json&amp;quot;,
      &amp;quot;mergecontext&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;output_location&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;output_location_type&amp;quot;:&amp;quot;none&amp;quot;,
      &amp;quot;mustacheAltTags&amp;quot;:false,
      &amp;quot;x&amp;quot;:1340,
      &amp;quot;y&amp;quot;:120,
      &amp;quot;wires&amp;quot;:[
         [
            
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;10c6a2fd.57b7dd&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;switch&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;has state ben processed&amp;quot;,
      &amp;quot;property&amp;quot;:&amp;quot;data.attributes.state_machine_processed&amp;quot;,
      &amp;quot;propertyType&amp;quot;:&amp;quot;msg&amp;quot;,
      &amp;quot;rules&amp;quot;:[
         {
            &amp;quot;t&amp;quot;:&amp;quot;true&amp;quot;
         },
         {
            &amp;quot;t&amp;quot;:&amp;quot;else&amp;quot;
         }
      ],
      &amp;quot;checkall&amp;quot;:&amp;quot;true&amp;quot;,
      &amp;quot;repair&amp;quot;:false,
      &amp;quot;outputs&amp;quot;:2,
      &amp;quot;x&amp;quot;:770,
      &amp;quot;y&amp;quot;:140,
      &amp;quot;wires&amp;quot;:[
         [
            &amp;quot;41cecb0c.bbfbe4&amp;quot;
         ],
         [
            &amp;quot;f4044d2d.8f0a6&amp;quot;
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;f4044d2d.8f0a6&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;function&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;set topic as payload&amp;quot;,
      &amp;quot;func&amp;quot;:&amp;quot;msg.payload = msg.topic\nmsg.data = {\n    \&amp;quot;attributes\&amp;quot;: {\n        \&amp;quot;state_machine_processed\&amp;quot; : true\n    }\n}\nreturn msg;&amp;quot;,
      &amp;quot;outputs&amp;quot;:1,
      &amp;quot;noerr&amp;quot;:0,
      &amp;quot;x&amp;quot;:1040,
      &amp;quot;y&amp;quot;:140,
      &amp;quot;wires&amp;quot;:[
         [
            &amp;quot;bbcdb78.3712348&amp;quot;
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;41cecb0c.bbfbe4&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;function&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;ebd2b060.28c27&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;create color data as payload&amp;quot;,
      &amp;quot;func&amp;quot;:&amp;quot;whitePayload = {\n    \&amp;quot;data\&amp;quot;: {\n        \&amp;quot;kelvin\&amp;quot;: 6000\n    }\n}\n\nyellowPayload = {\n    \&amp;quot;data\&amp;quot;: {\n        \&amp;quot;kelvin\&amp;quot;: 3000\n    }\n}\n\nlet payloadToUse;\nswitch(msg.topic) {\n    case \&amp;quot;white\&amp;quot;: \n        payloadToUse = whitePayload;\n        break;\n    case \&amp;quot;yellow\&amp;quot;:\n        payloadToUse = yellowPayload;\n        break;\n    default:\n    \n        break;\n}\n\nmsg.payload = payloadToUse;\nreturn msg;&amp;quot;,
      &amp;quot;outputs&amp;quot;:1,
      &amp;quot;noerr&amp;quot;:0,
      &amp;quot;x&amp;quot;:1060,
      &amp;quot;y&amp;quot;:100,
      &amp;quot;wires&amp;quot;:[
         [
            &amp;quot;f1efc70d.5de9e8&amp;quot;
         ]
      ]
   },
   {
      &amp;quot;id&amp;quot;:&amp;quot;b0807fb3.bda6d&amp;quot;,
      &amp;quot;type&amp;quot;:&amp;quot;server&amp;quot;,
      &amp;quot;z&amp;quot;:&amp;quot;&amp;quot;,
      &amp;quot;name&amp;quot;:&amp;quot;Home Assistant&amp;quot;
   }
]
&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title><![CDATA[Enable the new Android Auto UI]]></title><description><![CDATA[In the last few weeks the new Android Auto UI is being released by Google but it's not yet available for everyone. There are some tricks you…]]></description><link>https://www.emanuelepapa.dev/enable-the-new-android-auto-ui/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/enable-the-new-android-auto-ui/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 25 Aug 2019 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/97ba1b9d1f927a7d7260a335de1c1070/Ecran_d_accueil_Android_Auto.jpeg" length="47162" type="image/jpeg"/><content:encoded>&lt;p&gt;In the last few weeks the new Android Auto UI is being released by Google but it’s not yet available for everyone. There are some tricks you can use to force enable it before Google releases it to everyone.&lt;/p&gt;&lt;p&gt;One of these tricks is explained &lt;a href=&quot;https://www.reddit.com/r/AndroidAuto/comments/ckhnrs/force_android_auto_new_interface/&quot;&gt;here on Reddit&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;It works on my OP6 running Android Pie, but those commands need to be executed each time I want to use the new Android Auto UI, otherwise I see the old one.&lt;/p&gt;&lt;p&gt;To overcome this little issue I made a Tasker profile to automatically run those commands when the smartphone is connected to a power source, so they are executed before Android Auto starts.&lt;/p&gt;&lt;p&gt;Here the Tasker profile! Just import it in your Tasker and enjoy the new Android Auto UI!&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;TaskerData sr=&amp;quot;&amp;quot; dvi=&amp;quot;1&amp;quot; tv=&amp;quot;5.8.3&amp;quot;&amp;gt;
    &amp;lt;Profile sr=&amp;quot;prof21&amp;quot; ve=&amp;quot;2&amp;quot;&amp;gt;
        &amp;lt;cdate&amp;gt;1566724247086&amp;lt;/cdate&amp;gt;
        &amp;lt;edate&amp;gt;1566724464417&amp;lt;/edate&amp;gt;
        &amp;lt;id&amp;gt;21&amp;lt;/id&amp;gt;
        &amp;lt;mid0&amp;gt;17&amp;lt;/mid0&amp;gt;
        &amp;lt;mid1&amp;gt;17&amp;lt;/mid1&amp;gt;
        &amp;lt;nme&amp;gt;On power change AA to new UI&amp;lt;/nme&amp;gt;
        &amp;lt;State sr=&amp;quot;con0&amp;quot; ve=&amp;quot;2&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg0&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
        &amp;lt;/State&amp;gt;
    &amp;lt;/Profile&amp;gt;
    &amp;lt;Task sr=&amp;quot;task17&amp;quot;&amp;gt;
        &amp;lt;cdate&amp;gt;1566723956355&amp;lt;/cdate&amp;gt;
        &amp;lt;edate&amp;gt;1566724006352&amp;lt;/edate&amp;gt;
        &amp;lt;id&amp;gt;17&amp;lt;/id&amp;gt;
        &amp;lt;nme&amp;gt;New AA UI&amp;lt;/nme&amp;gt;
        &amp;lt;Action sr=&amp;quot;act0&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;123&amp;lt;/code&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;sqlite3 /data/data/com.google.android.gms/databases/phenotype.db &amp;quot;INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, user, name, intVal, committed) VALUES (&amp;#x27;com.google.android.projection.gearhead&amp;#x27;,45592854,0,0,&amp;#x27;&amp;#x27;,&amp;#x27;Boardwalk__launch_experiment_id&amp;#x27;,15592854,1);&amp;quot;&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg1&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg2&amp;quot; val=&amp;quot;1&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg3&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg4&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg5&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
        &amp;lt;/Action&amp;gt;
        &amp;lt;Action sr=&amp;quot;act1&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;123&amp;lt;/code&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;sqlite3 /data/data/com.google.android.gms/databases/phenotype.db &amp;quot;INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, user, name, boolVal, committed) VALUES (&amp;#x27;com.google.android.projection.gearhead&amp;#x27;,45592854,0,0,&amp;#x27;&amp;#x27;,&amp;#x27;Boardwalk__enabled&amp;#x27;,1,1);&amp;quot;&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg1&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg2&amp;quot; val=&amp;quot;1&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg3&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg4&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg5&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
        &amp;lt;/Action&amp;gt;
    &amp;lt;/Task&amp;gt;
&amp;lt;/TaskerData&amp;gt;
&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title><![CDATA[Make your washing machine smarter with Home Assistant]]></title><description><![CDATA[Do you own an old washing machine and do you ever think about how to make your washing machine smarter?
Would you have better information…]]></description><link>https://www.emanuelepapa.dev/make-your-washing-machine-smarter-with-home-assistant/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/make-your-washing-machine-smarter-with-home-assistant/</guid><category><![CDATA[Smart Home]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sat, 01 Jun 2019 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/328229d10abad429317abd67344c87fd/funny-launderette-laundromat-2371.jpeg" length="348961" type="image/jpeg"/><content:encoded>&lt;p&gt;Do you own an old washing machine and do you ever think about how to make your washing machine smarter?
Would you have better information about the current washing cycle?&lt;/p&gt;&lt;p&gt;Well, you can do it today, you just need a couple of things:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;a smart plug with energy monitoring feature&lt;/li&gt;&lt;li&gt;an &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; instance running somewhere (e.g. on your Raspberry)&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;smart-plug-integration&quot;&gt;Smart plug integration&lt;/h3&gt;&lt;p&gt;Each smart plug is different from another so you have to figure out how to integrate your into Home Assistant.&lt;/p&gt;&lt;p&gt;In my setup I used a &lt;a href=&quot;https://www.tp-link.com/en/home-networking/smart-plug/hs110/&quot;&gt;TP-Link HS-110&lt;/a&gt; and I integrated it into my Home Assistant as shown &lt;a href=&quot;https://www.tp-link.com/en/home-networking/smart-plug/hs110/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;After the successful integration you should have a new sensor which reports the current consumption, and this is the one you are going to use to monitor your washing machine!&lt;/p&gt;&lt;h3 id=&quot;washing-machine-integration&quot;&gt;Washing machine integration&lt;/h3&gt;&lt;p&gt;The following snippet of code is an Home Assistant package, you can just copy-paste it into a &lt;em&gt;whatever_you_want.yaml&lt;/em&gt; file into your packages directory; the only thing you need to do is to replace &lt;em&gt;PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE&lt;/em&gt; with your current consumption sensor name!&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;homeassistant:
  customize_glob:
    &amp;quot;*washing_machine*&amp;quot;:
      icon: mdi:washing-machine

input_text:
  washing_machine_enriched_status:
    name: &amp;#x27;Washing Machine Enriched Status&amp;#x27;
    initial: &amp;quot;Off&amp;quot;
    
sensor:
  - platform: template
    sensors:
      washing_machine_status:
        friendly_name: &amp;#x27;Washing Machine Status&amp;#x27;
        value_template:  &amp;gt;
          {% if states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float &amp;gt; 3.0 %}
            {{ &amp;quot;Running&amp;quot; }}
          {% elif states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float &amp;gt; 1 %}
            {{ &amp;quot;On&amp;quot; }}
          {% else %}
            {{ &amp;quot;Off&amp;quot; }}
          {% endif %}
      washing_machine_info:
        friendly_name: &amp;#x27;Washing Machine Info&amp;#x27;
        entity_id: sensor.time
        value_template:  &amp;gt;
          {%- macro as_formatted_elapsed_time(now, other_date_time) %}
          {% set duration = as_timestamp(now) - as_timestamp(other_date_time) %}
          {% set seconds = (duration % 60) | int %}
          {% set minutes = ((duration / 60) | int) % 60 %}
          {% set hours = (duration / 3600) | int %}
          {{ [hours, &amp;quot;hours&amp;quot;, minutes, &amp;quot;minutes&amp;quot;, seconds, &amp;quot;seconds&amp;quot;] | join(&amp;#x27; &amp;#x27;) }}
          {%- endmacro %}
          {% if states.input_text.washing_machine_enriched_status.state == &amp;quot;Running&amp;quot; %}
            Washing machine running for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
          {% elif states.input_text.washing_machine_enriched_status.state == &amp;quot;On_After_Running&amp;quot; %}
            Clothes left in the washing machine for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
          {% elif states.input_text.washing_machine_enriched_status.state == &amp;quot;On_After_Off&amp;quot; %}
            {{ &amp;quot;Washing machine ready to start&amp;quot; }}
          {% else %}
            {{ &amp;quot;Washing machine is off&amp;quot; }}
          {% endif %}

automation:
  - id: &amp;#x27;1556314846684&amp;#x27;
    alias: Refresh Washing Machine Info
    initial_state: true
    trigger:
      platform: state
      entity_id: sensor.washing_machine_status
    action:
      - service: input_text.set_value
        data_template:
          entity_id: input_text.washing_machine_enriched_status
          value:  &amp;gt;
            {% if trigger.to_state.state == &amp;quot;Running&amp;quot; %}
              {{ &amp;quot;Running&amp;quot; }}
            {% elif trigger.to_state.state == &amp;quot;On&amp;quot; and trigger.from_state.state == &amp;quot;Running&amp;quot; %}
              {{ &amp;quot;On_After_Running&amp;quot; }}
            {% elif trigger.to_state.state == &amp;quot;On&amp;quot; and trigger.from_state.state == &amp;quot;Off&amp;quot; %}
              {{ &amp;quot;On_After_Off&amp;quot; }}
            {% else %}
              {{ &amp;quot;Off&amp;quot; }}
            {% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;detailed-explanation&quot;&gt;Detailed explanation&lt;/h3&gt;&lt;p&gt;Let me explain a bit about what this package does:&lt;/p&gt;&lt;p&gt;Here we are just telling Home Assistant to use a washing machine icon for all the sensors containing the string &lt;em&gt;washing_machine&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;homeassistant:
  customize_glob:
    &amp;quot;*washing_machine*&amp;quot;:
      icon: mdi:washing-machine
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here we declare a sensor, &lt;em&gt;washing_machine_status&lt;/em&gt;, to keep track of the status of the washing machine. There are three possible status: &lt;em&gt;Running&lt;/em&gt;, &lt;em&gt;On&lt;/em&gt; and &lt;em&gt;Off&lt;/em&gt;. You would need to tune the watts value according to your washing machine to detect when it’s running or just turned on.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;washing_machine_status:
        friendly_name: &amp;#x27;Washing Machine Status&amp;#x27;
        value_template:  &amp;gt;
          {% if states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float &amp;gt; 3.0 %}
            {{ &amp;quot;Running&amp;quot; }}
          {% elif states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float &amp;gt; 1 %}
            {{ &amp;quot;On&amp;quot; }}
          {% else %}
            {{ &amp;quot;Off&amp;quot; }}
          {% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here we setup an automation to store in an &lt;em&gt;input_text&lt;/em&gt; (couldn’t find a better way) the &lt;em&gt;enriched status&lt;/em&gt; of our washing machine: the &lt;em&gt;enriched status&lt;/em&gt; is a status which keeps track of the previous one. So for example, if the washing machine was off and you turn it on, the status would be &lt;em&gt;On&lt;/em&gt;, but the &lt;em&gt;enriched status&lt;/em&gt; would be &lt;em&gt;On_After_Off&lt;/em&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;automation:
  - id: &amp;#x27;1556314846684&amp;#x27;
    alias: Refresh Washing Machine Info
    initial_state: true
    trigger:
      platform: state
      entity_id: sensor.washing_machine_status
    action:
      - service: input_text.set_value
        data_template:
          entity_id: input_text.washing_machine_enriched_status
          value:  &amp;gt;
            {% if trigger.to_state.state == &amp;quot;Running&amp;quot; %}
              {{ &amp;quot;Running&amp;quot; }}
            {% elif trigger.to_state.state == &amp;quot;On&amp;quot; and trigger.from_state.state == &amp;quot;Running&amp;quot; %}
              {{ &amp;quot;On_After_Running&amp;quot; }}
            {% elif trigger.to_state.state == &amp;quot;On&amp;quot; and trigger.from_state.state == &amp;quot;Off&amp;quot; %}
              {{ &amp;quot;On_After_Off&amp;quot; }}
            {% else %}
              {{ &amp;quot;Off&amp;quot; }}
            {% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is just the simple declaration of the variable holding the &lt;em&gt;enriched status&lt;/em&gt; of the washing machine.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;input_text:
  washing_machine_enriched_status:
    name: &amp;#x27;Washing Machine Enriched Status&amp;#x27;
    initial: &amp;quot;Off&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here we declare another sensor, &lt;em&gt;washing_machine_info&lt;/em&gt;, which will keep us informed of the washing machine’s current status and will also tell us more information (e.g. for how long the washing machine is running). The sensor will be updated every minute since it has a dependency on &lt;em&gt;sensor.time&lt;/em&gt;: you can customize this behaviour adding another automation to refresh it faster or slower, but I think every minute is enough. &lt;em&gt;as_formatted_elapsed_time&lt;/em&gt; is a macro used to display the elapsed time between two moments.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;washing_machine_info:
    friendly_name: &amp;#x27;Washing Machine Info&amp;#x27;
    entity_id: sensor.time
    value_template:  &amp;gt;
        {%- macro as_formatted_elapsed_time(now, other_date_time) %}
        {% set duration = as_timestamp(now) - as_timestamp(other_date_time) %}
        {% set seconds = (duration % 60) | int %}
        {% set minutes = ((duration / 60) | int) % 60 %}
        {% set hours = (duration / 3600) | int %}
        {{ [hours, &amp;quot;hours&amp;quot;, minutes, &amp;quot;minutes&amp;quot;, seconds, &amp;quot;seconds&amp;quot;] | join(&amp;#x27; &amp;#x27;) }}
        {%- endmacro %}
        {% if states.input_text.washing_machine_enriched_status.state == &amp;quot;Running&amp;quot; %}
          Washing machine running for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
        {% elif states.input_text.washing_machine_enriched_status.state == &amp;quot;On_After_Running&amp;quot; %}
          Clothes left in the washing machine for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
        {% elif states.input_text.washing_machine_enriched_status.state == &amp;quot;On_After_Off&amp;quot; %}
          {{ &amp;quot;Washing machine ready to start&amp;quot; }}
        {% else %}
            {{ &amp;quot;Washing machine is off&amp;quot; }}
        {% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, you can just put your new sensor &lt;em&gt;washing_machine_info&lt;/em&gt; into your UI and you will get very useful information about your washing machine, making it actually smarter.&lt;/p&gt;&lt;p&gt;Furthermore, you can use the statuses we collect here, and their time-based changes, to make other automations like send you a Telegram notification when the washing machine cycle ends, but at the moment that’s left to your imagination!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Build your smart car’s on-board computer!]]></title><description><![CDATA[Reading this post you will be able to create your smart car’s on-board computer, for example you will be to able to ask your Google…]]></description><link>https://www.emanuelepapa.dev/build-your-smart-cars-on-board-computer/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/build-your-smart-cars-on-board-computer/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sat, 14 Jul 2018 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/479561700e6bc7f9885e3c32cea440b7/action-asphalt-back-light-315938-1500x1000.jpeg" length="180947" type="image/jpeg"/><content:encoded>&lt;p&gt;Reading this post you will be able to create your smart car’s on-board computer, for example you will be to able to ask your Google Assistant (from your device or from an Android Auto head unit) the temperature of your car’s coolant liquid! Woa!&lt;/p&gt;&lt;p&gt;What do you need:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;an Android device (with Google Assistant or Android Auto since your going to use this in your car)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=org.prowl.torque&quot;&gt;Torque&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm&quot;&gt;Tasker&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.joaomgcd.join&quot;&gt;Join&lt;/a&gt;&lt;/li&gt;&lt;li&gt;an &lt;a href=&quot;https://ifttt.com/&quot;&gt;IFTTT&lt;/a&gt; account&lt;/li&gt;&lt;li&gt;an OBD2 device&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Such a long list of stuff!&lt;/p&gt;&lt;p&gt;Let’s start!&lt;/p&gt;&lt;h3 id=&quot;ifttt-and-tasker-integration&quot;&gt;IFTTT and Tasker integration&lt;/h3&gt;&lt;p&gt;Thanks to &lt;em&gt;Join&lt;/em&gt;, a new app from &lt;em&gt;joaomgcd&lt;/em&gt;, you can create a URL to push information to your devices in a very easy and powerful way! Read his &lt;a href=&quot;http://forum.joaoapps.com/index.php?resources/integrate-google-assistant-with-tasker-using-join-ifttt.206/&quot;&gt;well written tutorial here&lt;/a&gt; and you will be able to generate a URL to use in the Webhook &lt;em&gt;that&lt;/em&gt; action of &lt;em&gt;IFTTT&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;As reference, I’d like to report &lt;a href=&quot;https://www.reddit.com/r/tasker/comments/3arbeh/how_to_hookup_tasker_to_ifttt_using_autoremote/&quot;&gt;the old way of doing this&lt;/a&gt;, so everyone can read it and understand how much Join has simplified and improved this process!&lt;/p&gt;&lt;h3 id=&quot;tasker-and-torque-integration&quot;&gt;Tasker and Torque integration&lt;/h3&gt;&lt;p&gt;Now, you will need to make &lt;em&gt;Tasker&lt;/em&gt; able to read your car’s data using &lt;em&gt;Torque&lt;/em&gt;. To accomplish this, open &lt;em&gt;Torque&lt;/em&gt; and set it up to log your car’s data to a file on your sdcard (you can do it making changes in the &lt;em&gt;Settings – Data Logging and Upload&lt;/em&gt; section).&lt;/p&gt;&lt;p&gt;&lt;em&gt;Tasker&lt;/em&gt; will now be able to read your car’s data reading that log file using the following task which you could import into it.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;TaskerData sr=&amp;quot;&amp;quot; dvi=&amp;quot;1&amp;quot; tv=&amp;quot;5.2.bf6&amp;quot;&amp;gt;
    &amp;lt;Task sr=&amp;quot;task43&amp;quot;&amp;gt;
        &amp;lt;cdate&amp;gt;1432225457845&amp;lt;/cdate&amp;gt;
        &amp;lt;edate&amp;gt;1531597970703&amp;lt;/edate&amp;gt;
        &amp;lt;id&amp;gt;43&amp;lt;/id&amp;gt;
        &amp;lt;nme&amp;gt;ReadCarEngineCoolantTemperature&amp;lt;/nme&amp;gt;
        &amp;lt;pri&amp;gt;100&amp;lt;/pri&amp;gt;
        &amp;lt;Action sr=&amp;quot;act0&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;342&amp;lt;/code&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg0&amp;quot; val=&amp;quot;5&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg1&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;/storage/emulated/0/torqueLogs/trackLog.csv&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg2&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;%TORQUE_LOG_EXISTS&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg3&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
        &amp;lt;/Action&amp;gt;
        &amp;lt;Action sr=&amp;quot;act1&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;123&amp;lt;/code&amp;gt;
            &amp;lt;se&amp;gt;false&amp;lt;/se&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;tail -1 /storage/emulated/0/torqueLogs/trackLog.csv&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg1&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg2&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg3&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;%OBDLOG&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg4&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg5&amp;quot; ve=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;ConditionList sr=&amp;quot;if&amp;quot;&amp;gt;
                &amp;lt;Condition sr=&amp;quot;c0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;
                    &amp;lt;lhs&amp;gt;%TORQUE_LOG_EXISTS&amp;lt;/lhs&amp;gt;
                    &amp;lt;op&amp;gt;0&amp;lt;/op&amp;gt;
                    &amp;lt;rhs&amp;gt;true&amp;lt;/rhs&amp;gt;
                &amp;lt;/Condition&amp;gt;
            &amp;lt;/ConditionList&amp;gt;
        &amp;lt;/Action&amp;gt;
        &amp;lt;Action sr=&amp;quot;act2&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;590&amp;lt;/code&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;%OBDLOG&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg1&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;,&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg2&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;ConditionList sr=&amp;quot;if&amp;quot;&amp;gt;
                &amp;lt;Condition sr=&amp;quot;c0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;
                    &amp;lt;lhs&amp;gt;%TORQUE_LOG_EXISTS&amp;lt;/lhs&amp;gt;
                    &amp;lt;op&amp;gt;0&amp;lt;/op&amp;gt;
                    &amp;lt;rhs&amp;gt;true&amp;lt;/rhs&amp;gt;
                &amp;lt;/Condition&amp;gt;
            &amp;lt;/ConditionList&amp;gt;
        &amp;lt;/Action&amp;gt;
        &amp;lt;Action sr=&amp;quot;act3&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;547&amp;lt;/code&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;%EngineCoolantTemp&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg1&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;%OBDLOG2&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg2&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg3&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg4&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;ConditionList sr=&amp;quot;if&amp;quot;&amp;gt;
                &amp;lt;Condition sr=&amp;quot;c0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;
                    &amp;lt;lhs&amp;gt;%TORQUE_LOG_EXISTS&amp;lt;/lhs&amp;gt;
                    &amp;lt;op&amp;gt;0&amp;lt;/op&amp;gt;
                    &amp;lt;rhs&amp;gt;true&amp;lt;/rhs&amp;gt;
                &amp;lt;/Condition&amp;gt;
            &amp;lt;/ConditionList&amp;gt;
        &amp;lt;/Action&amp;gt;
        &amp;lt;Action sr=&amp;quot;act4&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;559&amp;lt;/code&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;Engine coolant temperature is %EngineCoolantTemp degrees&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg1&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;default:default&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg2&amp;quot; val=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg3&amp;quot; val=&amp;quot;5&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg4&amp;quot; val=&amp;quot;5&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg5&amp;quot; val=&amp;quot;1&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg6&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg7&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;ConditionList sr=&amp;quot;if&amp;quot;&amp;gt;
                &amp;lt;Condition sr=&amp;quot;c0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;
                    &amp;lt;lhs&amp;gt;%TORQUE_LOG_EXISTS&amp;lt;/lhs&amp;gt;
                    &amp;lt;op&amp;gt;0&amp;lt;/op&amp;gt;
                    &amp;lt;rhs&amp;gt;true&amp;lt;/rhs&amp;gt;
                &amp;lt;/Condition&amp;gt;
            &amp;lt;/ConditionList&amp;gt;
        &amp;lt;/Action&amp;gt;
        &amp;lt;Action sr=&amp;quot;act5&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;559&amp;lt;/code&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;E C U connection not established!&amp;lt;/Str&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg1&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;default:default&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg2&amp;quot; val=&amp;quot;3&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg3&amp;quot; val=&amp;quot;5&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg4&amp;quot; val=&amp;quot;5&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg5&amp;quot; val=&amp;quot;1&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg6&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg7&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;ConditionList sr=&amp;quot;if&amp;quot;&amp;gt;
                &amp;lt;Condition sr=&amp;quot;c0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;
                    &amp;lt;lhs&amp;gt;%TORQUE_LOG_EXISTS&amp;lt;/lhs&amp;gt;
                    &amp;lt;op&amp;gt;0&amp;lt;/op&amp;gt;
                    &amp;lt;rhs&amp;gt;false&amp;lt;/rhs&amp;gt;
                &amp;lt;/Condition&amp;gt;
            &amp;lt;/ConditionList&amp;gt;
        &amp;lt;/Action&amp;gt;
    &amp;lt;/Task&amp;gt;
&amp;lt;/TaskerData&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This task will check for that log file on your external memory and:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;if it founds it, it make your speakers say the last engine coolant temperature read by &lt;em&gt;Torque&lt;/em&gt;&lt;/li&gt;&lt;li&gt;if it doesn’t found it, it make your speakers say there is no connection between &lt;em&gt;Torque&lt;/em&gt; and you car ECU (your OBD2 adapter).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Another task that you will need is one that deletes that log file when the &lt;em&gt;Torque&lt;/em&gt; app closes, so the next time you ask &lt;em&gt;Tasker&lt;/em&gt; to read that file your are sure the data is new, and if there is no file, a new connection with your car wasn’t established.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;TaskerData sr=&amp;quot;&amp;quot; dvi=&amp;quot;1&amp;quot; tv=&amp;quot;5.2.bf6&amp;quot;&amp;gt;
    &amp;lt;Task sr=&amp;quot;task47&amp;quot;&amp;gt;
        &amp;lt;cdate&amp;gt;1525023939110&amp;lt;/cdate&amp;gt;
        &amp;lt;edate&amp;gt;1529001057169&amp;lt;/edate&amp;gt;
        &amp;lt;id&amp;gt;47&amp;lt;/id&amp;gt;
        &amp;lt;nme&amp;gt;DeleteTorqueLog&amp;lt;/nme&amp;gt;
        &amp;lt;pri&amp;gt;100&amp;lt;/pri&amp;gt;
        &amp;lt;Action sr=&amp;quot;act0&amp;quot; ve=&amp;quot;7&amp;quot;&amp;gt;
            &amp;lt;code&amp;gt;406&amp;lt;/code&amp;gt;
            &amp;lt;Str sr=&amp;quot;arg0&amp;quot; ve=&amp;quot;3&amp;quot;&amp;gt;torqueLogs/trackLog.csv&amp;lt;/Str&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg1&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
            &amp;lt;Int sr=&amp;quot;arg2&amp;quot; val=&amp;quot;0&amp;quot;/&amp;gt;
        &amp;lt;/Action&amp;gt;
    &amp;lt;/Task&amp;gt;
&amp;lt;/TaskerData&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;google-assistant-and-ifttt-and-tasker-and-torque-integration&quot;&gt;Google Assistant and IFTTT and Tasker and Torque integration&lt;/h3&gt;&lt;p&gt;Now the last part! We have all the pieces, let’s put them together!&lt;/p&gt;&lt;p&gt;Login to &lt;em&gt;IFTTT&lt;/em&gt; and create a new applet!&lt;/p&gt;&lt;p&gt;Click on &lt;em&gt;this&lt;/em&gt; and look for &lt;em&gt;Google Assistant&lt;/em&gt;, then click on it. Select the &lt;em&gt;Say a simple phrase&lt;/em&gt; trigger and write your custom question(s) and response; click then on &lt;em&gt;Create trigger&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Click now on &lt;em&gt;that&lt;/em&gt; and look for &lt;em&gt;Webhooks&lt;/em&gt;, then click on it. Select the &lt;em&gt;Make a web request&lt;/em&gt; action and use the URL you generated as explained in the &lt;a href=&quot;http://forum.joaoapps.com/index.php?resources/integrate-google-assistant-with-tasker-using-join-ifttt.206/&quot;&gt;joaomgcd’s tutorial&lt;/a&gt;. Click on &lt;em&gt;Create action&lt;/em&gt;, then on &lt;em&gt;Finish&lt;/em&gt; and your recipe is ready!&lt;/p&gt;&lt;p&gt;Open your &lt;em&gt;Google Assistant&lt;/em&gt; and say the phrase you just put in the &lt;em&gt;IFTTT&lt;/em&gt; recipe, your device should say you’re not connected to the ECU, you got the push notification, that’s great!&lt;/p&gt;&lt;p&gt;Now go to your car, turn it on, make &lt;em&gt;Torque&lt;/em&gt; pair with your OBD2 adapter…. when it’s ready ask &lt;em&gt;Google Assistant&lt;/em&gt; again to read your engine coolant temperature and now you should hear it! &lt;/p&gt;&lt;p&gt;Wonderful, it works!!!&lt;/p&gt;&lt;h3 id=&quot;further-improvements-of-your-smart-car&quot;&gt;Further improvements of your smart car&lt;/h3&gt;&lt;p&gt;The recipe on &lt;em&gt;IFTTT&lt;/em&gt; and the task on &lt;em&gt;Tasker&lt;/em&gt; could be improved by using parameters and so making you able to ask for and listen to different details of your car, which could now be called a smart car!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[AndroidAutoAlerts – Simple alerts app for Android Auto]]></title><description><![CDATA[AndroidAutoAlerts is a very simple app for Android Auto I have developed to test my Android Auto unit. Please read through the article to…]]></description><link>https://www.emanuelepapa.dev/androidautoalerts-simple-alerts-app-for-android-auto/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/androidautoalerts-simple-alerts-app-for-android-auto/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 10 Sep 2017 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/b6332409e6c99eb0a57188f5f70209f9/pexels-black-ice-1314544.jpeg" length="676240" type="image/jpeg"/><content:encoded>&lt;p&gt;AndroidAutoAlerts is a very simple app for Android Auto I have developed to test my Android Auto unit. Please read through the article to check it out!&lt;/p&gt;&lt;h2 id=&quot;what-it-does-and-how-it-works&quot;&gt;What it does and how it works&lt;/h2&gt;&lt;p&gt;As I said, the app is very simple and at the moment it has only one functionality: it will notify the driver if the speed limit is exceeded. Wow!&lt;/p&gt;&lt;p&gt;The driver can set the maximum speed limit through the app’s settings (you can also choose the name of your virtual assistant!)&lt;/p&gt;&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1080px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/7e5887820b81044484997e2b2e099fbb/302a4/androidautoalerts_settings.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:177.89473684210526%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAIAAAAGkY33AAAACXBIWXMAAAsTAAALEwEAmpwYAAABq0lEQVR42u2PXS/DUBjHzwdygREEezNsizHdu4RIGO5ExCcRMolNRBaWma5C5z0h7k29bJ0LXIl1tqq9tKuGJ1uCRELDbX/5neY5Oc+/zzmo276mtQRrWv21msBXa9r86p6gawT3eInRSbJ3INw/GIFmsytkcoYGxggDtoraLXiLOQI2myKqznDVhq5ws2m9vRc32Df1WLRviOzACJNnS923obXiGiuuxwg4Rd3uXffEkc5GGj074zOnI1Mno9Mnw5PHBmdMb49pbaQOIzX929CgxUi9nYRCZ4vBF2rUZCY6HGSdIdpoJNwThw7vgdO7bx3eq++MqrqqEhU/ik+Rb/liYeXKH0wsBhNzgYv5pcu5pcvZxXPfMvWrSBT5XPYxl00/ZR7yL9lnlgElSXx7k34VcdxLPE6BV9eJ27t7mk7R9E0+XxCEMs8LP4tKpRLDMLkcK8E48VWqUJYH4nn+tUJ1D1v4HS8PVCwW0+l0JpNhKhQKBVEU5U7mOO7sLE5RFJ1KJZJJlmXhFgK8WE4YFjwSAjCwenlBNuh7d1k2qPwPlLASVsJKWAn/hXdiOKAuH4SGMQAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Settings screen&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/7e5887820b81044484997e2b2e099fbb/302a4/androidautoalerts_settings.png&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/7e5887820b81044484997e2b2e099fbb/0e2fe/androidautoalerts_settings.png 285w,/static/7e5887820b81044484997e2b2e099fbb/432e7/androidautoalerts_settings.png 570w,/static/7e5887820b81044484997e2b2e099fbb/302a4/androidautoalerts_settings.png 1080w&quot; sizes=&quot;(max-width: 1080px) 100vw, 1080px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;Settings screen&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Then, go back to the main screen and start the notification service. A service will run (and will keep running in the foreground using a system notification when you exit the app or attach your device to your Android Auto unit).&lt;/p&gt;&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1080px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/c6c4c545578a144dfb031676f9f25f54/302a4/androidautoalerts_mainscreen.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:177.89473684210526%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAIAAAAGkY33AAAACXBIWXMAAAsTAAALEwEAmpwYAAACUklEQVR42u1T7U8SARy+v6fNLUuLNSaaJoghkCJUZG5gSLO1WdPqa1uf+pC9ujJbbhavg5OQTWFzSm2trdJ4yQ/cAQFnZ7zbcUQH/Q7SYWZttvWJZ89+e3h+z3P3YwyEL9M1d03XcR/X8yb3bxI0ODzRdK/KolCh6ouz4tNGidIskOmPKwwdvfo+7YxYaUS4IsshgelAm+HgMeMWG9uN4BwWmFqkaGs3Ku6bbT2BCk+94AhNHKG5WWqF1ZFOMyJUzp27vKQddQ9cWgKqhhfVw4vKoYXBUff5K+5ulatJYudJ7Ud7HJxO29C1V1dvvGnsmBmf8mtGXiLcLptc41IMumQDTmn/vEztlGucojNzJ7Ws2S531LdZG/hoA99a12LpUbvOXljY12Qeuf5a0j+P3HuyfOvh+7FHLG9PsBM+3pmA+Q7E3cnlB09XKhyfWhkr+yBu3n8LYaRUYkql4uYsbtfM7mS3CE3n90zk2z+gVq6V917O5/O/1X8pf98BhmFg/qkMj4cESZIfPB6vz/dxddXn9wO9fr/H6wXzM0lCoPoKZKtJ0zS8AcPxZzqd2WKxOxw6gwG12fQG43OdHswAhhWLRYjly/hZrj4vu7GRSqXT6Uw8kUgmWZlKpTKZLOhsNguZQhkVgVAUhePBaDQWCoViBAG3RaPRSCQSjyfAJIi19fUvayxgQxIEEYvFYBsOh5PJJPtmqoyvFJWjadhhGA4T1iDC4U/BYCgQwOBm0BDL5XKVPNzPlgtVqHx5mBCid6CwHbv+zpX1L07tj1Er/9fyD07mmEhTH6gtAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Main screen&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/c6c4c545578a144dfb031676f9f25f54/302a4/androidautoalerts_mainscreen.png&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/c6c4c545578a144dfb031676f9f25f54/0e2fe/androidautoalerts_mainscreen.png 285w,/static/c6c4c545578a144dfb031676f9f25f54/432e7/androidautoalerts_mainscreen.png 570w,/static/c6c4c545578a144dfb031676f9f25f54/302a4/androidautoalerts_mainscreen.png 1080w&quot; sizes=&quot;(max-width: 1080px) 100vw, 1080px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;Main screen&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1080px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/7720e424dca358ad4cc6f5d9957056b4/302a4/androidautoalerts_foregroundnotification.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:177.89473684210526%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAIAAAAGkY33AAAACXBIWXMAAAsTAAALEwEAmpwYAAAFMklEQVR42o1VaVcTVxiej0U9KsyShGSWe29CSICERJYALmQBDKJCWaL1g/4Gj/1W/4lyxLZH26/W05/Rg0CgCIhshSSEnbDbZ2ZIDLXt8TnvufPcd+673ncSjhJa4XK7nC6ikbzQIv5Zo5+sqADRVA1CCOXc7srbXbfb2tqJpu9xwjTIEwolRFEUv8//+PFjSpin0uvz+VVF5XBIkRUwahwCMX07HDIhuhdotLwXr7fKdK1BpREO5xobQ8FA0IxTXV3jZE53hbu5qZlRBkEQFAXXyJExJ87X1gZCjSHd+PKlyw8fPuq61XWu5FxTqOnpD08FXoDxkyff26y2clt5OByBC1EQPR4vpayslI9EIt09PZIocbFYLBKJhlvD0Wj05s14PN4ZjYDGTMGrSDiirxFzjYbD4fb29rZYG5xye3t76VRqf38/t7eXMwHV/v7h4SGUR0dHn/I4OTkpEJNz6+vryeT41tbW6mp2bQ279TXjsbu7i7XY+EtwODQ7O5tMJmdmPgBTU1MzMzNzc3Mgk5OT4+PjqVSqOOwZY+S29JeO5eXl+YWFpaWlVDoNgu1KKoUt1mw2m8lklldWdnO5M8bHx8ewPzg4wIpa9XL3D/YMmEoT2OYMzUkRuI2NDRS8aeD0kQdeneq3toqVBXAoKTk+PjI6Mj4x8efkJDjqR7ZIdWJiYmxs7P37qZHR0bFkEpmvnQWXWV1dNgAvqAoVAqtofTZr0NSKibyyGNyX5e3vnaKgyHfkn+BwJJ1Oozrc8vb2NlYUign59BXgMFEfP35E2nNz8/PzC4uLi7j2leWVr7HXhwTDMDU9PfthVp+SmRn0Znp6Go3FhKHP/zoen+8Zd4FUNzb1O9PT3tDvY2t7O53OpDOZ/5lQzuzWgd4UE4dme3K5024htdx/QP8wUCiuBEOKdXFxCfUjbOEE2ryzu2Nge+csONwevgT0Ciu6tbCwgK/CvF5s4Qh6VIH4O19An7A/DLwbHh4bHRsbHR0dGRl5NzI8PAwNegmeSae3jREtTKlJuKPjI/0jRm83N1fX17MbG+tbm5hDpPj5Z6BIChq9YW8GX75+/frVi5dvng+9HXz522CePB968+yFKeCmvH0+9OrHX34d+un3Z0M/Dw5xlvOlSmVFE1HjF8Q7JUJ3iXi3ROgqEW4Za7FA0/1NqdN25Txr6pRs/vMCx9vs3upAa6y/JVDPeFuzJHeKar+ofSdp9yUtIWn9eemTtHuiXOu8VseaH8j0poVwomSz2lmpw2ORyUXBclGwlglWhbf5eHuYd9wRlAFB6SuS2xbWXyb3l8rfCgp3WbDaHAr+DXjByos2SJlovSRa4eUibxF4yxXenigy7ldcvcTTS7xIhCtFKNEmWOwIWGqELYgglZdJ5Rd4i48vT/BKnymC2idqfaIKRxzilBnRjLUgurFkdUBAkEWNUJ4Q0Au11zDTXQgqJ2vUoWqyBiEmN7bErlGLrFhlBcShUZEQv0YHmCtBWII6BwgboE5OIYxWhSyesJ15NcrUqhZ71Q0Hq6ykrC5YVxe84iLMSVi7TEMyqdVov8/f1xZNtLQkXC7ORj01V3tIfbdUed3u9MU6Ez29DyR3qMXlfhTtgDQ6PfjXbvBH48GrQRdtuXbdWx9ubo3d99dyDloheaLVLT3E22DT3FWhzvobdy3MF3RXdnXEuzo6A26vlREt0NAeCHoYDdY3xNpvxVuj9zxepE0d1GXVPChYJcSqVkiqG0qIypwKZSAyYSrVnFQvXqWsJgA3nm7K/gZf/MXlAgJPhwAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Notification of active ForegroundService&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/7720e424dca358ad4cc6f5d9957056b4/302a4/androidautoalerts_foregroundnotification.png&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/7720e424dca358ad4cc6f5d9957056b4/0e2fe/androidautoalerts_foregroundnotification.png 285w,/static/7720e424dca358ad4cc6f5d9957056b4/432e7/androidautoalerts_foregroundnotification.png 570w,/static/7720e424dca358ad4cc6f5d9957056b4/302a4/androidautoalerts_foregroundnotification.png 1080w&quot; sizes=&quot;(max-width: 1080px) 100vw, 1080px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;Notification of active ForegroundService&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;&lt;p&gt;So, now, using the speed taken from the GPS sensor, when the app catches you going faster a notification will be displayed on your Android Auto unit from your virtual assistant! Tap it and listen to what it has to say to you! Please slow down, you have exceeded your speed limit!!&lt;/p&gt;&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:802px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/052e64ab83e2680092778ced3f38660e/5a6dd/androidautoalerts_notification.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:63.859649122807014%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAADHklEQVR42mM4dfn2mUt3Tl++dxKE7l659/zmozfXHry+fP/VlfuvL959ef7WMyA6c/3p0YsPjl6AoPsHTt8+ev4hQ2fP1tYJexsn7K+ecLBq8pHyKceLJ53I7jue1H08vfdEYtexsPq9fqVbgyu2RVZudUxa6piw2CB4rn38YqvIxQyNxTMnVi2a0rGuqHaZV9rM9t5dle07vTMX+uYtCypbXzjpeGLHgbDanaFV25JadvkWrHdNW24YPMc2dpFV5CIGM5/alMi2bTM3Xr/1+Ni5O1cv3jl9+sbJC/fOXHpw/uqTO4/eXbv/+sbDt5fvvD5z7fmZy0/OXX6SXLdF3XuGUfB8BiHbMlHrko6J696+fvXp0+c7d+/fuHnzy+fPP358B4LPnz5//fLlGxB/+fLv15/H5/c+v3m8e/Yh05BpFuHzGCSdyjisiqLrFv7////L1x/v3n8EomfPXz9/+ebl63dPn7968fLNi1dvn754/f7D5zfPn75+87phyhajwDaTwD4GCadSLuui1LYV79+8+vr1688fP75+/gxEP79/+48B/v37ByQTWlew25SK2lUxiDtV8NqWeRfOWr//7Np9ZzYePL9696lVu0+tP3Rh65FLWw5f3HL00pZjl7ecun7gyv0j1x+euPnYr3o+t1uluGcdg6hDubhzjYB9OadDJadDEZdTIaddEad9IadHJbtPDbtvNXtADXtwPXt4k0hSt3ndQpfWFU71i+Xju4T86xnEHKpEHWrEHGrEPRvEfWsl/Osk/JvFA1slQtsko7sk43okEnokknolUvpEk3vksid59a6LnLbds3ONRFwvg4h5hrBppphFtphtrohjtqhTtphzgZhzsZhriYhLoYhbkZhPmZhfuVhgpWhguYB/mVZ6p0vtLI/qGSrRjQxaOhY6etaSkurSMlpKKkZKKobS0poyslqS0hoaWhYaWuaSQK6cDpAEctU0TKVktKTldDR1LNU0zBk09Y10DMyk5JRlFFSV1XU0dAxl5FXllNSl5VW09IyBSFpORU5RHUjqGJpp6ZsAGUDFmnpGGlqGAKcal7Hi2yZhAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Android Auto Head Unit notification&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/052e64ab83e2680092778ced3f38660e/5a6dd/androidautoalerts_notification.png&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/052e64ab83e2680092778ced3f38660e/0e2fe/androidautoalerts_notification.png 285w,/static/052e64ab83e2680092778ced3f38660e/432e7/androidautoalerts_notification.png 570w,/static/052e64ab83e2680092778ced3f38660e/5a6dd/androidautoalerts_notification.png 802w&quot; sizes=&quot;(max-width: 802px) 100vw, 802px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;Android Auto Head Unit notification&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;&lt;h2 id=&quot;where-to-get-it&quot;&gt;Where to get it&lt;/h2&gt;&lt;p&gt;At the moment, the app is not following all of the Android Auto requirements (see them &lt;a href=&quot;https://developer.android.com/develop/quality-guidelines/auto-app-quality.html&quot;&gt;here&lt;/a&gt;) and Google only allows music and messaging Android Auto apps. For this reason I had to create the notification like a messaging app, like you are going to reply to it, even if you are not. Furthermore, I’m not going to publish it on Google Play at the moment (till apps of this kink will be accepted on it), but you can check its source code which I shared &lt;a href=&quot;https://github.com/ema987/androidautoalerts&quot;&gt;here on github&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;future-development&quot;&gt;Future development&lt;/h2&gt;&lt;p&gt;Let’s wait for Google to accept other kinds of apps on the Android Auto platform and let’s hope this platform will become more and more interactive, full of useful apps (maybe some which can control car’s features, would be amazing!) and used by more and more people! I already love it, can’t live without it!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using Spring Data to persist a Set of Enums in a Many-To-Many relationship]]></title><description><![CDATA[Usually, when we write a Many-To-Many relationship it is between 2 entities. Sometimes, it could happen that we have an entity which has a…]]></description><link>https://www.emanuelepapa.dev/using-spring-data-to-persist-a-set-of-enums-in-a-many-to-many-relationship/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/using-spring-data-to-persist-a-set-of-enums-in-a-many-to-many-relationship/</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Thu, 24 Aug 2017 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/68efd8a00f6f76b0c7b1ed34d4b2a44d/data-5933101_1920.jpeg" length="559018" type="image/jpeg"/><content:encoded>&lt;p&gt;Usually, when we write a Many-To-Many relationship it is between 2 entities. Sometimes, it could happen that we have an entity which has a field which is a set of enum. In this case, we need to approach the problem differently and Spring comes to help.&lt;/p&gt;&lt;p&gt;Let’s see an example where we have two classes: &lt;em&gt;Person&lt;/em&gt; and &lt;em&gt;Skill&lt;/em&gt; (e.g. swimming, running, etc).&lt;/p&gt;&lt;h3 id=&quot;many-to-many-between-entities-example&quot;&gt;Many-To-Many between entities example&lt;/h3&gt;&lt;p&gt;The following code shows what the situation is when &lt;em&gt;Person&lt;/em&gt; and &lt;em&gt;Skill&lt;/em&gt; are both entities.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity 
public class Person {
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 
  private Long id;

  private String name;

  @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = &amp;quot;person_skill&amp;quot;, joinColumns = {
    @JoinColumn(name = &amp;quot;person_id&amp;quot;)
  }, inverseJoinColumns = {
    @JoinColumn(name = &amp;quot;skill_id&amp;quot;)
  }) 
  private Set &amp;lt;Skill&amp;gt; skillSet;
}

@Entity 
public class Skill {
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 
  private Long id;
  private String name;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To use this mapping, you would have 3 tables. You can create them with the following SQL code.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE `person` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT, 
  `name` VARCHAR(45) NOT NULL, 
  PRIMARY KEY (`id`)
);

CREATE TABLE `skill` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT, 
  `name` VARCHAR(45) NOT NULL, 
  PRIMARY KEY (`id`)
);

CREATE TABLE `person_skill` (
  `person_id` BIGINT(20) NOT NULL, 
  `skill_id` BIGINT(20) NOT NULL, 
  PRIMARY KEY (`person_id`, `skill_id`), 
  INDEX `skill_fk_idx` (`skill_id` ASC), 
  CONSTRAINT `person_fk` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 
  CONSTRAINT `skill_fk` FOREIGN KEY (`skill_id`) REFERENCES `skill` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;many-to-many-between-entity-and-enum-example&quot;&gt;Many-To-Many between entity and enum example&lt;/h3&gt;&lt;p&gt;The following code shows what the situation is when &lt;em&gt;Person&lt;/em&gt; is an entity and &lt;em&gt;Skill&lt;/em&gt; is an enum.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Entity 
public class Person {
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 
  private Long id;

  private String name;

  @ElementCollection(targetClass = Skill.class) 
  @CollectionTable(name = &amp;quot;person_skill&amp;quot;, joinColumns = @JoinColumn(name = &amp;quot;person_id&amp;quot;)) 
  @Enumerated(EnumType.STRING) 
  @Column(name = &amp;quot;skill_name&amp;quot;) 
  private Set &amp;lt;Skill&amp;gt; skillSet;
}

public enum Skill {
  RUNNING,
  SWIMMING
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To use this mapping, you would have only 2 tables, because &lt;em&gt;Skill&lt;/em&gt; is not an entity and would not have its own table. You can create them with the following SQL code.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE `person` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT, 
  `name` VARCHAR(45) NOT NULL, 
  PRIMARY KEY (`id`)
);

CREATE TABLE `person_skill` (
  `person_id` BIGINT(20) NOT NULL, 
  `skill_name` VARCHAR(45) NOT NULL, 
  PRIMARY KEY (`person_id`, `skill_name`), 
  CONSTRAINT `person_fk` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see here, you don’t have the table skill because &lt;em&gt;Skill&lt;/em&gt; is not an entity but only an enum; you only have to create the table which represents the relationship between &lt;em&gt;Person&lt;/em&gt; and &lt;em&gt;Skill&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Furthermore, since we added the annotation &lt;em&gt;@Enumerated(EnumType.STRING)&lt;/em&gt;, &lt;em&gt;Spring Data&lt;/em&gt; will save the name of the entity in the database (look, we put a &lt;em&gt;VARCHAR&lt;/em&gt; column).
If you prefer, you could use &lt;em&gt;EnumType.ORDINAL&lt;/em&gt; and &lt;em&gt;Spring Data&lt;/em&gt; will save the ordinal value of the entity (1,2,3,etc..), so change the column to accept a numeric value.&lt;/p&gt;&lt;p&gt;That’s it!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[MobaXterm - Enhanced SSH client for Windows]]></title><description><![CDATA[I've always struggled myself each time I had to use SSH on Windows (yes, at least half of the time I use it, I like it). Just few months ago…]]></description><link>https://www.emanuelepapa.dev/mobaxterm-enhanced-ssh-client-for-windows/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/mobaxterm-enhanced-ssh-client-for-windows/</guid><category><![CDATA[Windows]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Mon, 31 Jul 2017 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/abdc555c4644438ae6c5b146889d300b/OpenBSD_starting_SSH_server.jpeg" length="1185435" type="image/jpeg"/><content:encoded>&lt;p&gt;I’ve always struggled myself each time I had to use SSH on Windows (yes, at least half of the time I use it, I like it). Just few months ago Windows released the &lt;a href=&quot;https://msdn.microsoft.com/it-it/commandline/wsl/about&quot;&gt;Bash Ubuntu integration&lt;/a&gt;, which is very well integrated with the Windows environment and it’s a very cool feature, even if it is still a beta!&lt;/p&gt;&lt;p&gt;Anyway, since I was always looking for a better alternative than PuTTY, I finally found an alternative which is extremely worth to be mentioned, &lt;a href=&quot;http://mobaxterm.mobatek.net/&quot;&gt;MobaXterm&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;It has a very cool &lt;a href=&quot;http://mobaxterm.mobatek.net/features.html&quot;&gt;list of features&lt;/a&gt; and has both a free and a professional editions.&lt;/p&gt;&lt;p&gt;It is a lot more than only a featured SSH client, so, really,  give it a look, it’s really worth it!&lt;/p&gt;&lt;p&gt;Thank you MobaXterm!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Android Contextual Action Bar]]></title><description><![CDATA[In this post you will learn how to implement a  Contextual Action Bar  (CAB) which will be useful to do actions on multiple items you have…]]></description><link>https://www.emanuelepapa.dev/android-contextual-action-bar/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/android-contextual-action-bar/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 30 Jul 2017 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/635627a889dacb17b50617bac6fb89ba/android.jpeg" length="158803" type="image/jpeg"/><content:encoded>&lt;p&gt;In this post you will learn how to implement a &lt;em&gt;Contextual Action Bar&lt;/em&gt; (CAB) which will be useful to do actions on multiple items you have selected in a &lt;em&gt;RecyclerView&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;The &lt;a href=&quot;https://developer.android.com/guide/topics/ui/menus.html#CAB&quot;&gt;&lt;em&gt;Contextual Action Mode&lt;/em&gt;&lt;/a&gt; represents a contextual mode of the user interface and focuses user interaction toward performing contextual actions. In the case of a &lt;em&gt;RecyclerView&lt;/em&gt; which shows a list of item, &lt;em&gt;Contextual Action Mode&lt;/em&gt; is triggered after a long press on one of this items: this causes the &lt;em&gt;Contextual Action Bar&lt;/em&gt; to appear at the top of the screen so then the user can interact with its actions.&lt;/p&gt;&lt;p&gt;Let’s follow these steps!&lt;/p&gt;&lt;h3 id=&quot;initial-setup&quot;&gt;Initial setup&lt;/h3&gt;&lt;p&gt;First of all, create a &lt;em&gt;RecyclerView&lt;/em&gt; and its adapter in the usual way you do it. Here in this example I will use a &lt;em&gt;RecyclerView&lt;/em&gt; which shows &lt;em&gt;Authentication&lt;/em&gt; items (it is a class I made for a project, it doesn’t matter for the purpose of the example, use whatever you want, and change the names accordingly).&lt;/p&gt;&lt;h3 id=&quot;theme-changes&quot;&gt;Theme changes&lt;/h3&gt;&lt;p&gt;Open your &lt;em&gt;styles.xml&lt;/em&gt; file and add the following lines:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;!-- It should be true otherwise action mode will not overlay toolbar --&amp;gt;
&amp;lt;item name=&amp;quot;windowActionModeOverlay&amp;quot;&amp;gt;true&amp;lt;/item&amp;gt;
&amp;lt;!-- For Custom Action Mode Background Color/Drawable --&amp;gt;
&amp;lt;item name=&amp;quot;actionModeBackground&amp;quot;&amp;gt;@color/colorAccent&amp;lt;/item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Comments should be self-explanatory.&lt;/p&gt;&lt;h3 id=&quot;menu-file&quot;&gt;Menu file&lt;/h3&gt;&lt;p&gt;We need a menu file which contains the available actions for that &lt;em&gt;RecyclerView&lt;/em&gt;. Let’s create a file into the res/menu directory like the following:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;menu
    xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;
    xmlns:app=&amp;quot;http://schemas.android.com/apk/res-auto&amp;quot;&amp;gt;
    &amp;lt;item android:id=&amp;quot;@+id/action_delete&amp;quot; android:icon=&amp;quot;@drawable/ic_delete_white_24dp&amp;quot; android:title=&amp;quot;@string/action_delete&amp;quot; app:showAsAction=&amp;quot;always&amp;quot; /&amp;gt;
&amp;lt;/menu&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We added a delete action that will be shown on the &lt;code&gt;CAB&lt;/code&gt;.&lt;/p&gt;&lt;h3 id=&quot;adapter-changes&quot;&gt;Adapter changes&lt;/h3&gt;&lt;p&gt;Our adapter we created in the first step needs to be able to keep trace of the elements we select. To do this, let’s modify our adapter and create a new field&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private final SparseBooleanArray selectedItemsIds;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and initialize it in our constructor&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;selectedItemsIds = new SparseBooleanArray();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, let’s create an interface which will be useful for all the &lt;em&gt;Adapters&lt;/em&gt; which need the &lt;em&gt;CAB&lt;/em&gt;, and make our &lt;em&gt;Adapter&lt;/em&gt; implements it. Please note this is a generic interface, so adapt it to the model you are displaying in your adapter.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface ActionModeAdapterCallbacks&amp;lt;T&amp;gt; {
    void toggleSelection(int position);
    void clearSelections();
    int getSelectedCount();
    List&amp;lt;T&amp;gt; getSelectedItems();
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is what the implementation should look like:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override public void toggleSelection(final int position) {
    if (selectedItemsIds.get(position)) {
        selectedItemsIds.delete(position);
    } else {
        selectedItemsIds.put(position, true);
    }
    notifyItemChanged(position);
}

@Override public void clearSelections() {
    selectedItemsIds.clear();
    notifyDataSetChanged();
}

@Override public int getSelectedCount() {
    return selectedItemsIds.size();
}

@Override public List &amp;lt; Authentication &amp;gt; getSelectedItems() {
    final List &amp;lt; Authentication &amp;gt; selectedItemList = new LinkedList &amp;lt; &amp;gt; ();
    for (int i = 0; i &amp;lt; selectedItemsIds.size(); i++) {
        selectedItemList.add(authenticationList.get(selectedItemsIds.keyAt(i)));
    }
    return selectedItemList;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we also need the &lt;em&gt;Adapter&lt;/em&gt; to change the view state when an item gets selected to show the user a visual feedback. To do this we can use a &lt;a href=&quot;https://developer.android.com/reference/android/graphics/drawable/StateListDrawable.html&quot;&gt;&lt;em&gt;StateListDrawable&lt;/em&gt;&lt;/a&gt; and in the &lt;em&gt;onBindViewHolder()&lt;/em&gt; method add this line.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;holder.itemView.setActivated(selectedItemsIds.get(position));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Create a new xml file inside the &lt;em&gt;res/drawable&lt;/em&gt; directory:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;selector
    xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;&amp;gt;
    &amp;lt;item android:drawable=&amp;quot;@color/colorPrimaryDark&amp;quot; android:state_activated=&amp;quot;true&amp;quot; /&amp;gt;
    &amp;lt;item android:drawable=&amp;quot;@android:color/transparent&amp;quot; /&amp;gt;
&amp;lt;/selector&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and assign it to the root view element of the layout file which represents your adapter’s row item:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;android:background=&amp;quot;@drawable/statelist_item_background&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Doing so, each time an item is selected or deselected, the adapter will change its color accordingly.&lt;/p&gt;&lt;h3 id=&quot;fragmentactivity-changes&quot;&gt;Fragment/Activity changes&lt;/h3&gt;&lt;p&gt;Now, we need to make our &lt;em&gt;Fragment/Activity&lt;/em&gt; class aware that we can trigger the &lt;em&gt;Contextual Action Mode&lt;/em&gt;. To do so, let’s create an interface and call it &lt;em&gt;ActionModeViewCallbacks&lt;/em&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface ActionModeViewCallbacks {
    void onListItemSelect(final int position);
    void onDestroyActionMode();
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This interface has 2 methods:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;onListItemSelect(final int position)&lt;/em&gt;: to be used after a long click on an item to trigger the &lt;em&gt;Action Mode&lt;/em&gt; (or after a single click on an item, if Action Mode was already triggered, to select this new item, too);&lt;/li&gt;&lt;li&gt;&lt;em&gt;onDestroyActionMode()&lt;/em&gt;: reset actionMode variable to null;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Now, let’s create another interface which extends from this one:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface ListAuthenticationActionModeViewCallbacks extends ActionModeViewCallbacks {
    void onDeleteActionClicked();
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This new interface has another method, &lt;em&gt;onDeleteActionClicked()&lt;/em&gt;, which is where the presenter gets called (check &lt;em&gt;MVP&lt;/em&gt; if you are not aware what &lt;em&gt;MVP&lt;/em&gt; and a &lt;em&gt;Presenter&lt;/em&gt; are) and asked to delete the items the user has selected (if you have more than a single action, you need to create more methods, each for any action you have).&lt;/p&gt;&lt;p&gt;This interface must be implemented by the &lt;em&gt;Fragment/Activity&lt;/em&gt; which wants to use the &lt;em&gt;Contextual Action Mode&lt;/em&gt;. The implementation looks like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public void onListItemSelect(final int position) {
    listAuthenticationAdapter.toggleSelection(position);

    final boolean hasCheckedItems = listAuthenticationAdapter.getSelectedCount() &amp;gt; 0;

    if (hasCheckedItems &amp;amp;&amp;amp; actionMode == null) {
        // there are some selected items, start the actionMode
        actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(new ListAuthenticationToolbarActionModeCallback(this, this, listAuthenticationAdapter));
    } else if (!hasCheckedItems &amp;amp;&amp;amp; actionMode != null) {
        // there no selected items, finish the actionMode
        actionMode.finish();
    }

    if (actionMode != null) {
        //set action mode title on item selection
        actionMode.setTitle(getString(R.string.cab_selected, listAuthenticationAdapter.getSelectedCount()));
    }

}

@Override
public void onDestroyActionMode() {
    actionMode = null;
}

@Override
public void onDeleteActionClicked() {
    presenter.delete(listAuthenticationAdapter.getSelectedItems());
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;where &lt;code&gt;R.string.cab_selected&lt;/code&gt; is&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;string name=&amp;quot;cab_selected&amp;quot;&amp;gt;%1$d selected&amp;lt;/string&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In doing &lt;em&gt;startSupportActionMode()&lt;/em&gt; we need to pass a &lt;em&gt;ActionMode.Callback&lt;/em&gt; item that I haven’t explained yet. Let’s create another class which implements that interface.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ListAuthenticationToolbarActionModeCallback implements ActionMode.Callback {
    private final ActionModeViewCallbacks actionModeViewCallbacks;
    private final ListAuthenticationAdapter listAuthenticationAdapter;

    public ListAuthenticationToolbarActionModeCallback(final ActionModeViewCallbacks actionModeViewCallbacks, final ListAuthenticationAdapter listAuthenticationAdapter) {
        this.actionModeViewCallbacks = actionModeViewCallbacks;
        this.listAuthenticationAdapter = listAuthenticationAdapter;
    }

    @Override public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
        mode.getMenuInflater().inflate(R.menu.listauthentication, menu);
        return true;
    }

    @Override public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
        menu.findItem(R.id.action_delete).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        return true;
    }

    @Override public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
        switch (item.getItemId()) {
        case R.id.action_delete:
            actionModeViewCallbacks.onDeleteActionClicked();
            mode.finish();
            return true;
        }
        return false;
    }

    @Override public void onDestroyActionMode(final ActionMode mode) {
        actionModeViewCallbacks.onDestroyActionMode();
        listAuthenticationAdapter.clearSelections();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Please have a look on the &lt;a href=&quot;https://developer.android.com/reference/android/view/ActionMode.Callback.html&quot;&gt;official documentation&lt;/a&gt; here to understand how &lt;em&gt;ActionMode.Callback&lt;/em&gt; works.&lt;/p&gt;&lt;h3 id=&quot;screenshots&quot;&gt;Screenshots&lt;/h3&gt;&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1080px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/594c5768a39a7914071e86164d15c356/302a4/cab_initial_state.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:177.89473684210526%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAIAAAAGkY33AAAACXBIWXMAAAsTAAALEwEAmpwYAAACMUlEQVR42u2TwU/TUBzH3x9EohGjIHGTGec23ZgyCGqEO5w8mKjEkzEePGsioCYgJotTNiOdGA+cvHjAZTAXsmymm1s72q6brbTda5+/FrbYg9qO6MlPfv3l2/f6ze/12xQFxuLDkeW+oceHPU8O/VR9Qwue88s376xfm30/c30tNPFiauZN+HIiOB4PjMdHp16BRifDK8cDiYHgy8GQrQaCCdgKXVn1RpMjk2nfxVTkKnUqmoJbb3TFO2IWClxam77xwT/x1hdLnx6zFax4L6wOj6Y9UeiUB3SMAgErsOWLUehY8HV48t3Rs8kjZ35R/mS/3xT9/pQpOgUazS1mHz7Nzi9tzS1tLjzbAjFv9k0nhQghhgm2hE6ITszuCNMsis1cLp/7nN/Y+MQwLMfxsIixruvG7wvBUE3TWq0Wx3HQFUWBW8MZiBwABAN37NTr9R0HgBHVarVqtfrVouoGMO6nDeF0BcbYRdrNZjNvkclkaJpmWRYWzaz/GBhcqqqKoriXNiDLcufj/9W09+Jl7dQdAEZUqVToDuVymXYMGPePDS/Q7S6O3U27UChks1kYDh/cadrwHKTdaDQYhhEtJElyYe49bUEQeJ7n7PAOACMqlUqFnigWi0g7AP/N/8Ksqr2awWlgS/RgxlhjBFVRQSB3M4nRXlw3/HfxrefqN9mxGV6z3Yb/Tz93j0TuGydu737cdjdZJbr8gMKDs9L0I4lruDKbw78rirD9pck3NB3/AO8QhDvFkx93AAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;The initial situation before *Action Mode* is triggered&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/594c5768a39a7914071e86164d15c356/302a4/cab_initial_state.png&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/594c5768a39a7914071e86164d15c356/0e2fe/cab_initial_state.png 285w,/static/594c5768a39a7914071e86164d15c356/432e7/cab_initial_state.png 570w,/static/594c5768a39a7914071e86164d15c356/302a4/cab_initial_state.png 1080w&quot; sizes=&quot;(max-width: 1080px) 100vw, 1080px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;The initial situation before *Action Mode* is triggered&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1080px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/594c5768a39a7914071e86164d15c356/302a4/cab_initial_state.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:177.89473684210526%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAIAAAAGkY33AAAACXBIWXMAAAsTAAALEwEAmpwYAAACMUlEQVR42u2TwU/TUBzH3x9EohGjIHGTGec23ZgyCGqEO5w8mKjEkzEePGsioCYgJotTNiOdGA+cvHjAZTAXsmymm1s72q6brbTda5+/FrbYg9qO6MlPfv3l2/f6ze/12xQFxuLDkeW+oceHPU8O/VR9Qwue88s376xfm30/c30tNPFiauZN+HIiOB4PjMdHp16BRifDK8cDiYHgy8GQrQaCCdgKXVn1RpMjk2nfxVTkKnUqmoJbb3TFO2IWClxam77xwT/x1hdLnx6zFax4L6wOj6Y9UeiUB3SMAgErsOWLUehY8HV48t3Rs8kjZ35R/mS/3xT9/pQpOgUazS1mHz7Nzi9tzS1tLjzbAjFv9k0nhQghhgm2hE6ITszuCNMsis1cLp/7nN/Y+MQwLMfxsIixruvG7wvBUE3TWq0Wx3HQFUWBW8MZiBwABAN37NTr9R0HgBHVarVqtfrVouoGMO6nDeF0BcbYRdrNZjNvkclkaJpmWRYWzaz/GBhcqqqKoriXNiDLcufj/9W09+Jl7dQdAEZUqVToDuVymXYMGPePDS/Q7S6O3U27UChks1kYDh/cadrwHKTdaDQYhhEtJElyYe49bUEQeJ7n7PAOACMqlUqFnigWi0g7AP/N/8Ksqr2awWlgS/RgxlhjBFVRQSB3M4nRXlw3/HfxrefqN9mxGV6z3Yb/Tz93j0TuGydu737cdjdZJbr8gMKDs9L0I4lruDKbw78rirD9pck3NB3/AO8QhDvFkx93AAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;The situation after *Action Mode* was triggered by long press an item&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/594c5768a39a7914071e86164d15c356/302a4/cab_initial_state.png&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/594c5768a39a7914071e86164d15c356/0e2fe/cab_initial_state.png 285w,/static/594c5768a39a7914071e86164d15c356/432e7/cab_initial_state.png 570w,/static/594c5768a39a7914071e86164d15c356/302a4/cab_initial_state.png 1080w&quot; sizes=&quot;(max-width: 1080px) 100vw, 1080px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;The situation after *Action Mode* was triggered by long press an item&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1080px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/594c5768a39a7914071e86164d15c356/302a4/cab_initial_state.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:177.89473684210526%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAIAAAAGkY33AAAACXBIWXMAAAsTAAALEwEAmpwYAAACMUlEQVR42u2TwU/TUBzH3x9EohGjIHGTGec23ZgyCGqEO5w8mKjEkzEePGsioCYgJotTNiOdGA+cvHjAZTAXsmymm1s72q6brbTda5+/FrbYg9qO6MlPfv3l2/f6ze/12xQFxuLDkeW+oceHPU8O/VR9Qwue88s376xfm30/c30tNPFiauZN+HIiOB4PjMdHp16BRifDK8cDiYHgy8GQrQaCCdgKXVn1RpMjk2nfxVTkKnUqmoJbb3TFO2IWClxam77xwT/x1hdLnx6zFax4L6wOj6Y9UeiUB3SMAgErsOWLUehY8HV48t3Rs8kjZ35R/mS/3xT9/pQpOgUazS1mHz7Nzi9tzS1tLjzbAjFv9k0nhQghhgm2hE6ITszuCNMsis1cLp/7nN/Y+MQwLMfxsIixruvG7wvBUE3TWq0Wx3HQFUWBW8MZiBwABAN37NTr9R0HgBHVarVqtfrVouoGMO6nDeF0BcbYRdrNZjNvkclkaJpmWRYWzaz/GBhcqqqKoriXNiDLcufj/9W09+Jl7dQdAEZUqVToDuVymXYMGPePDS/Q7S6O3U27UChks1kYDh/cadrwHKTdaDQYhhEtJElyYe49bUEQeJ7n7PAOACMqlUqFnigWi0g7AP/N/8Ksqr2awWlgS/RgxlhjBFVRQSB3M4nRXlw3/HfxrefqN9mxGV6z3Yb/Tz93j0TuGydu737cdjdZJbr8gMKDs9L0I4lruDKbw78rirD9pck3NB3/AO8QhDvFkx93AAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;The final situation after the item &amp;quot;Auth 2&amp;quot; was deleted&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/594c5768a39a7914071e86164d15c356/302a4/cab_initial_state.png&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/594c5768a39a7914071e86164d15c356/0e2fe/cab_initial_state.png 285w,/static/594c5768a39a7914071e86164d15c356/432e7/cab_initial_state.png 570w,/static/594c5768a39a7914071e86164d15c356/302a4/cab_initial_state.png 1080w&quot; sizes=&quot;(max-width: 1080px) 100vw, 1080px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;The final situation after the item &amp;quot;Auth 2&amp;quot; was deleted&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;&lt;h3 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h3&gt;&lt;p&gt;Your &lt;em&gt;Contextual Action Mode&lt;/em&gt; should work now! I suggest you to create different menu files, &lt;em&gt;Fragment/ActivityToolbarActionModeCallback&lt;/em&gt; and &lt;em&gt;Fragment/ActivityActionModeViewCallbacks&lt;/em&gt; to keep everything separated.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Please note&lt;/strong&gt;: if you rotate the device, &lt;em&gt;Contextual Action Mode&lt;/em&gt; is lost, you need to save its state and restore it.
Another post will follow, hopefully soon!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Java concurrency explained with a few examples]]></title><description><![CDATA[Sometimes we write very high level code, sometimes we don't. I want to put here a link to a simple project which shows how to deal with…]]></description><link>https://www.emanuelepapa.dev/java-concurrency-explained-with-a-few-examples/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/java-concurrency-explained-with-a-few-examples/</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sat, 06 May 2017 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/0b917f8d9f10443e6672f871f0b9c109/pexels-lukas-hartmann-1127120.jpeg" length="2720638" type="image/jpeg"/><content:encoded>&lt;p&gt;Sometimes we write very high level code, sometimes we don’t. I want to put here a link to a simple project which shows how to deal with concurrency in &lt;em&gt;Java&lt;/em&gt; with a few examples.&lt;/p&gt;&lt;p&gt;The project is available on Github &lt;a href=&quot;https://github.com/ema987/concurrencyexample&quot;&gt;here&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Goal of the project is to retrieve the details and the balance of a player and show them at the same time: until you have collected both of them you have to wait before showing the user the information.&lt;/p&gt;&lt;p&gt;There are two different packages: in the first, &lt;em&gt;Futures&lt;/em&gt; and &lt;em&gt;Callables&lt;/em&gt; are being used, in the other, &lt;em&gt;Threads/Runnables&lt;/em&gt; and a &lt;em&gt;CountDownLatch&lt;/em&gt; are.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Futures&lt;/em&gt; and &lt;em&gt;Callables&lt;/em&gt; are the best option when you need to run a task which has a return value. I also made it in a different way using &lt;em&gt;Threads/Runnables&lt;/em&gt;, a &lt;em&gt;CountDownLatch&lt;/em&gt; and some listeners but these classes are usually used when you have tasks which won’t return any value.&lt;/p&gt;&lt;p&gt;Use the two &lt;em&gt;Main&lt;/em&gt; classes to run the examples. Have fun!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[AWS S3 custom policy]]></title><description><![CDATA[Do you want to restrict access on  S3  only to some users? Do you want to restrict the bucket they can access? Do you want to restrict the…]]></description><link>https://www.emanuelepapa.dev/aws-s3-custom-policy/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/aws-s3-custom-policy/</guid><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 19 Feb 2017 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/0ba4cc98b3c74768d9c6efa243ade4d5/pexels-lamar-belina-6160108.jpeg" length="6972079" type="image/jpeg"/><content:encoded>&lt;p&gt;Do you want to restrict access on &lt;em&gt;S3&lt;/em&gt; only to some users? Do you want to restrict the bucket they can access? Do you want to restrict the actions they can do? Let’s create an AWS S3 custom policy!&lt;/p&gt;&lt;p&gt;Login to your &lt;em&gt;AWS&lt;/em&gt; console, go to the &lt;em&gt;IAM&lt;/em&gt; console, choose &lt;em&gt;Policies&lt;/em&gt; from the left side menu, then click on &lt;em&gt;Create Policy&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Here you can create a new policy in 3 different ways, but what we will do today is following the first one, so click on &lt;em&gt;Copy an AWS Managed Policy&lt;/em&gt;. In the next screen select &lt;em&gt;AmazonS3FullAccess&lt;/em&gt;. Now, choose a policy name and description. Then, let’s write the policy document.&lt;/p&gt;&lt;p&gt;What I wanted to achieve was these:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;user A which could access only the development bucket;&lt;/li&gt;&lt;li&gt;user B which could access only the production bucket.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So I created 2 policies, one for each user, that look like the following:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
   &amp;quot;Version&amp;quot;:&amp;quot;2012-10-17&amp;quot;,
   &amp;quot;Statement&amp;quot;:[
      {
         &amp;quot;Effect&amp;quot;:&amp;quot;Allow&amp;quot;,
         &amp;quot;Action&amp;quot;:&amp;quot;s3:*&amp;quot;,
         &amp;quot;Resource&amp;quot;:&amp;quot;arn:aws:s3:::bucketname*&amp;quot;
      }
   ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Look at the * at the end of the bucket’s name. It is mandatory otherwise you won’t have permission the execute the listing operation and many other operations would fail due to this.&lt;/p&gt;&lt;p&gt;Another way was to create the policy this way:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
   &amp;quot;Version&amp;quot;:&amp;quot;2012-10-17&amp;quot;,
   &amp;quot;Statement&amp;quot;:[
      {
         &amp;quot;Effect&amp;quot;:&amp;quot;Allow&amp;quot;,
         &amp;quot;Action&amp;quot;:&amp;quot;s3:ListBucket&amp;quot;,
         &amp;quot;Resource&amp;quot;:&amp;quot;arn:aws:s3:::bucketname&amp;quot;
      },
      {
         &amp;quot;Effect&amp;quot;:&amp;quot;Allow&amp;quot;,
         &amp;quot;Action&amp;quot;:&amp;quot;s3:*&amp;quot;,
         &amp;quot;Resource&amp;quot;:&amp;quot;arn:aws:s3:::bucketname/*&amp;quot;
      }
   ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I write it only for completeness but I find the first way more compact to write and to read.&lt;/p&gt;&lt;p&gt;Please note you can add more attributes to the &lt;em&gt;Statement&lt;/em&gt; object to restrict even further the permissions.&lt;/p&gt;&lt;p&gt;Now, after you have successfully created a policy, assuming you have already created a &lt;em&gt;IAM&lt;/em&gt; user from the &lt;em&gt;IAM&lt;/em&gt; console, go to that user’s info and attach the policy to it using the &lt;em&gt;Permissions&lt;/em&gt; tab.&lt;/p&gt;&lt;p&gt;Do you want to test your new user and policy work as expected? Let’s assume you have created these settings:&lt;/p&gt;&lt;p&gt;User A can access only the development bucket;
User B can access only the production bucket.&lt;/p&gt;&lt;p&gt;This is what should happen:&lt;/p&gt;&lt;p&gt;User A tries to access the development bucket: permission granted;
User A tries to access the production bucket: permission denied;
User B tries to access the development bucket: permission denied;
User B tries to access the production bucket: permission granted.&lt;/p&gt;&lt;p&gt;Please refer to the &lt;a href=&quot;http://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html&quot;&gt;AWS docs&lt;/a&gt; for a complete list of attributes and actions to fully customize your policy.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Android NoteApp with Dagger and Retrofit]]></title><description><![CDATA[Today I show a simple Android project I wrote which shows the use of  Dagger ,  Retrofit  and some other stuff. The full source code of the…]]></description><link>https://www.emanuelepapa.dev/android-noteapp-with-dagger-and-retrofit/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/android-noteapp-with-dagger-and-retrofit/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Wed, 12 Oct 2016 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/570b19c8b96389ed746af332c3f7d950/pexels-bruno-bueno-3854816.jpeg" length="3987754" type="image/jpeg"/><content:encoded>&lt;p&gt;Today I show a simple Android project I wrote which shows the use of &lt;em&gt;Dagger&lt;/em&gt;, &lt;em&gt;Retrofit&lt;/em&gt; and some other stuff.&lt;/p&gt;&lt;p&gt;The full source code of the project is available for you &lt;a href=&quot;https://github.com/ema987/android-note-app&quot;&gt;here&lt;/a&gt;&lt;/p&gt;&lt;p&gt;The project’s main purposes are the following:&lt;/p&gt;&lt;p&gt;Show &lt;em&gt;Dagger2&lt;/em&gt; dependency injection;
Show &lt;em&gt;MVP&lt;/em&gt; architecture (made through &lt;em&gt;Dagger2&lt;/em&gt;)
Show the repository pattern used to cache server data
Show the creation and use of a CustomView&lt;/p&gt;&lt;p&gt;Furthermore, I have used ButterKnife to remove the boilerplate code needed to bind classes/views, and GreenDao to automatically create the SQLite repository/model classes.&lt;/p&gt;&lt;p&gt;This project is based on the Google Android Architecture samples which are available &lt;a href=&quot;https://github.com/googlesamples/android-architecture&quot;&gt;here&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[ApplicationContextException using SpringBoot with Gradle and IntelliJ IDEA]]></title><description><![CDATA[In my spare time I decided to have a better look to SpringBoot and in doing so I decided to leave back Maven and Eclipse and to move to…]]></description><link>https://www.emanuelepapa.dev/applicationcontextexception-using-springboot-with-gradle-and-intellij-idea/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/applicationcontextexception-using-springboot-with-gradle-and-intellij-idea/</guid><category><![CDATA[Development]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sat, 16 Apr 2016 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/4a67807d8514c23843391d50274e1c88/explosion-1325471_1920.jpeg" length="587540" type="image/jpeg"/><content:encoded>&lt;p&gt;In my spare time I decided to have a better look to SpringBoot and in doing so I decided to leave back Maven and Eclipse and to move to Gradle and IntelliJ, which I already use to develop Android applications.&lt;/p&gt;&lt;p&gt;During the first phase of my development I encountered a strange issue: it seems that IntelliJ IDEA will add wrong dependencies to your SpringBoot project which will cause it to crash at runtime.&lt;/p&gt;&lt;p&gt;This was the exception thrown and the full stack trace:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;org.springframework.context.ApplicationContextException: Unable to start embedded container; nested exception is org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:133) ~[spring-boot-1.3.3.RELEASE.jar:1.3.3.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-4.2.5.RELEASE.jar:4.2.5.RELEASE]
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118) ~[spring-boot-1.3.3.RELEASE.jar:1.3.3.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766) [spring-boot-1.3.3.RELEASE.jar:1.3.3.RELEASE]
    at org.springframework.boot.SpringApplication.createAndRefreshContext(SpringApplication.java:361) [spring-boot-1.3.3.RELEASE.jar:1.3.3.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-1.3.3.RELEASE.jar:1.3.3.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191) [spring-boot-1.3.3.RELEASE.jar:1.3.3.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1180) [spring-boot-1.3.3.RELEASE.jar:1.3.3.RELEASE]
    at com.sampleapp.SampleAppApplication.main(SampleAppApplication.java:16) [main/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_72]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_72]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_72]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_72]
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) [idea_rt.jar:na]
Caused by: org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.getEmbeddedServletContainerFactory(EmbeddedWebApplicationContext.java:185) ~[spring-boot-1.3.3.RELEASE.jar:1.3.3.RELEASE]
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.createEmbeddedServletContainer(EmbeddedWebApplicationContext.java:158) ~[spring-boot-1.3.3.RELEASE.jar:1.3.3.RELEASE]
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:130) ~[spring-boot-1.3.3.RELEASE.jar:1.3.3.RELEASE]
    ... 13 common frames omitted
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This stacktrace is related to &lt;em&gt;SpringBoot v1.3.3&lt;/em&gt; but I experienced the same issue even with &lt;em&gt;SpringBoot v1.4.0&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;In the end I found that IntelliJ was putting this line in the &lt;em&gt;build.gradle&lt;/em&gt; file:&lt;/p&gt;&lt;p&gt;&lt;code&gt;providedRuntime(&amp;#x27;org.springframework.boot:spring-boot-starter-tomcat&amp;#x27;)&lt;/code&gt;&lt;/p&gt;&lt;p&gt;After removing that line, the project ran without issues.&lt;/p&gt;&lt;p&gt;So if you are experiencing an &lt;em&gt;ApplicationContextException&lt;/em&gt; and you couldn’t find its cause, try to have a look at your &lt;em&gt;build.gradle&lt;/em&gt; file.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Windows 10 Wake On Lan]]></title><description><![CDATA[I was using the Wake On Lan feature of my motherboard brilliantly, but after having updated my system from Windows 7 to Windows 10 it…]]></description><link>https://www.emanuelepapa.dev/windows-10-wake-on-lan/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/windows-10-wake-on-lan/</guid><category><![CDATA[Windows]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sat, 26 Mar 2016 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/407cb2a9e2b48a53f83cb42dc916330e/cat-3043890_1920.jpeg" length="178555" type="image/jpeg"/><content:encoded>&lt;p&gt;I was using the Wake On Lan feature of my motherboard brilliantly, but after having updated my system from Windows 7 to Windows 10 it stopped working and I had to figure out why.&lt;/p&gt;&lt;p&gt;Here below are the steps you need to check if you want to enable the Wake On Lan on your computer.&lt;/p&gt;&lt;h3 id=&quot;enable-wake-on-lan-in-your-motherboard-settings&quot;&gt;Enable Wake On LAN in your motherboard settings&lt;/h3&gt;&lt;p&gt;First, you need to enter your BIOS/UEFI and check for Wake On Lan setting. It should be very simple, you should just find an option: the only thing you have to do is set it to Enabled.&lt;/p&gt;&lt;p&gt;Done!&lt;/p&gt;&lt;h3 id=&quot;enable-wake-on-lan-in-windows-10-network-device-settings&quot;&gt;Enable Wake On LAN in Windows 10 network device settings&lt;/h3&gt;&lt;p&gt;Search for &lt;em&gt;Device Manager&lt;/em&gt; from the Start Menu/toolbar and open it. Look for &lt;em&gt;Network Adapters&lt;/em&gt;, if you open it you will find the list of all the network adapters you have installed in your computer (ethernet, wifi, virtual ones, etc..)&lt;/p&gt;&lt;p&gt;Find the one you use to connect to your LAN, and right click on it, then choose &lt;em&gt;Properties&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;The following depends on your hardware/driver version: the point as always here is to find the Wake On Lan setting. You could find it under the &lt;em&gt;Advanced&lt;/em&gt; or &lt;em&gt;Power Management&lt;/em&gt; tabs. Also here you need to enable the Wake On Lan property, if any, and some other properties like &lt;em&gt;Wake on Magic Packet&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Done!&lt;/p&gt;&lt;h3 id=&quot;disable-windows-10-fast-startup&quot;&gt;Disable Windows 10 fast startup&lt;/h3&gt;&lt;p&gt;This step is only necessary if after have done the two previous ones the Wake On Lan isn’t yet working. Disabling this option could slightly increase your system startup but it was the only way to let my Wake On Lan work. By the way, I didn’t notice any difference between before and after startup times.&lt;/p&gt;&lt;p&gt;Search for &lt;em&gt;Device Manager&lt;/em&gt; from the Start Menu/toolbar and open it. Now click on &lt;em&gt;Choose what the power buttons do&lt;/em&gt; from the left menu. Disable &lt;em&gt;Turn on fast startup&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Done!&lt;/p&gt;&lt;p&gt;If everything is setup correctly you should be able to turn on your computer from all the other computer and devices (smartphones, tablets, etc) which are connected to your LAN. There are a lot of softwares for whatever Operating System you need to perform this operation. You can also install Team Viewer on the computer you want to wake and at least on one other computer in the LAN to use this one to turn on the target machine from the Team Viewer web interface very easily with just a click!&lt;/p&gt;&lt;p&gt;Any suggestions/questions, feel free to write a comment below!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Install Oracle Java JDK on Linux]]></title><description><![CDATA[In this post we will find out how to install the official Oracle Java JDK on Linux. First, you need to download the correct package for your…]]></description><link>https://www.emanuelepapa.dev/install-oracle-java-jdk-on-linux/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/install-oracle-java-jdk-on-linux/</guid><category><![CDATA[Linux]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sat, 13 Feb 2016 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/ac12119eefee51e85abbd845c7237ea5/Java-Debugging-Tips-881x441.jpeg" length="20018" type="image/jpeg"/><content:encoded>&lt;p&gt;In this post we will find out how to install the official Oracle Java JDK on Linux.&lt;/p&gt;&lt;p&gt;First, you need to download the correct package for your operating system.
Go &lt;a href=&quot;http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html&quot;&gt;here&lt;/a&gt; and download the Linux .tar.gz file for x86 or x64 platform.&lt;/p&gt;&lt;p&gt;At the time of writing this article, the current available version is Java SE Development Kit 8u74.&lt;/p&gt;&lt;p&gt;Now, copy the file you just downloaded to the folder &lt;em&gt;/usr/local/java&lt;/em&gt; by doing&lt;/p&gt;&lt;p&gt;&lt;code&gt;sudo cp -r FILE_NAME /usr/local/java/&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Now unpack the archive using this command&lt;/p&gt;&lt;p&gt;&lt;code&gt;sudo tar xvzf FILE_NAME&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Wait for the extraction to be completed, then remove the .tar.gz archive.&lt;/p&gt;&lt;p&gt;Now, to let the JDK folder be system-wide available, we need to add it to the &lt;em&gt;PATH&lt;/em&gt;. To do this, let’s run this command&lt;/p&gt;&lt;p&gt;&lt;code&gt;sudo gedit /etc/profile&lt;/code&gt;&lt;/p&gt;&lt;p&gt;and at the end of the file past these lines&lt;/p&gt;&lt;pre&gt;&lt;code&gt;JAVA_HOME=/usr/local/java/DIR_NAME
PATH=$PATH:$JAVA_HOME/bin
export JAVA_HOME
export PATH
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;where &lt;em&gt;DIR_NAME&lt;/em&gt; is the name of the directory which was created when you extracted the .tar.gz archive.&lt;/p&gt;&lt;p&gt;Now let’s reload our changes by running&lt;/p&gt;&lt;p&gt;&lt;code&gt;source /etc/profile&lt;/code&gt;&lt;/p&gt;&lt;p&gt;and it’s done!&lt;/p&gt;&lt;p&gt;If everything went out correctly you should be able to run&lt;/p&gt;&lt;p&gt;&lt;code&gt;java -version&lt;/code&gt;&lt;/p&gt;&lt;p&gt;and see your current installed Java version!&lt;/p&gt;&lt;p&gt;Happy development!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Custom Fragment Backstack in Android]]></title><description><![CDATA[Dealing with Fragments is always a pain: after some time I found a way to deal with them in a less painful way, creating a custom Fragment…]]></description><link>https://www.emanuelepapa.dev/custom-fragment-backstack-in-android/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/custom-fragment-backstack-in-android/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sun, 31 Jan 2016 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/46c57d2d42fac47cb74810b6edc20cd3/pexels-karolina-grabowska-4218583.jpeg" length="3318781" type="image/jpeg"/><content:encoded>&lt;p&gt;Dealing with Fragments is always a pain: after some time I found a way to deal with them in a less painful way, creating a custom Fragment Backstack.&lt;/p&gt;&lt;h3 id=&quot;how-to-create-your-custom-fragment-backstack-navigation&quot;&gt;How to create your custom Fragment Backstack navigation&lt;/h3&gt;&lt;p&gt;Put this code in your main Activity:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class MainActivity extends AppCompatActivity {
  private Stack &amp;lt; Fragment &amp;gt; fragmentStack;
  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    fragmentStack = new Stack &amp;lt; &amp;gt; ();
  }
  @Override public void onBackPressed() {
    fragmentStack.pop();
    if (fragmentStack.size() == 0) {
      super.onBackPressed();
    } else {
      showFragment(fragmentStack.lastElement(), false);
    }
  }
  public void showFragment(Fragment fragment, boolean addToStack) {
    if (addToStack) {
      fragmentStack.push(fragment);
    }
    getFragmentManager().beginTransaction().replace(R.id.container_fragment, fragment).commit();
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have a &lt;em&gt;fragmentStack&lt;/em&gt; field which is a &lt;em&gt;Stack&lt;/em&gt; of &lt;em&gt;Fragment&lt;/em&gt;: we use this to save the &lt;em&gt;Fragments&lt;/em&gt; we show in our app and we initialize it in the &lt;em&gt;onCreate()&lt;/em&gt; method of the main &lt;em&gt;Activity&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;In the &lt;em&gt;onBackPressed()&lt;/em&gt; method we remove the current &lt;em&gt;Fragment&lt;/em&gt; from the &lt;em&gt;Stack&lt;/em&gt; and then if there is another one, we show it, otherwise we close the app because there is nothing more to be shown.&lt;/p&gt;&lt;p&gt;The method &lt;em&gt;showFragment()&lt;/em&gt; is the one we use to show a &lt;em&gt;Fragment&lt;/em&gt; on the screen; we invoke it every time we need to change the visible &lt;em&gt;Fragment&lt;/em&gt;. If we want to save the current transaction being able to come back to the current &lt;em&gt;Fragment&lt;/em&gt; we pass &lt;em&gt;true&lt;/em&gt; as &lt;em&gt;addToStack&lt;/em&gt; value, otherwise we pass &lt;em&gt;false&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Have a look at the full source code on &lt;a href=&quot;/w3ma-github-repository-showcase&quot;&gt;my GitHub here&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Auto hide keyboard using Android EditText]]></title><description><![CDATA[Do you want to automatically hide your keyboard whenever you touch outside it? Please continue reading and you will find out how to do it…]]></description><link>https://www.emanuelepapa.dev/auto-hide-keyboard-using-android-edittext/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/auto-hide-keyboard-using-android-edittext/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Mon, 14 Dec 2015 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/54ad58a6bd3496e377fc5734fe73750b/pexels-alena-darmel-7862496.jpeg" length="1147730" type="image/jpeg"/><content:encoded>&lt;p&gt;Do you want to automatically hide your keyboard whenever you touch outside it? Please continue reading and you will find out how to do it!&lt;/p&gt;&lt;p&gt;First, we will create our &lt;em&gt;AutoHideKeyboardEditText&lt;/em&gt; class like the following:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class AutoHideKeyboardEditText extends EditText {
  public AutoHideKeyboardEditText(Context context) {
    super(context);
    init();
  }
  public AutoHideKeyboardEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }
  public AutoHideKeyboardEditText(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }
  private void init() {
    setOnFocusChangeListener(new OnFocusChangeListener() {
      @Override public void onFocusChange(View v, boolean hasFocus) {
        if (!hasFocus) {
          hideKeyboard(v);
        }
      }
    });
  }
  private void hideKeyboard(View view) {
    InputMethodManager inputMethodManager = (InputMethodManager) view.getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Whatever &lt;em&gt;Constructor&lt;/em&gt; is used, we set an &lt;em&gt;onFocusChangeListener&lt;/em&gt; to hide the keyboard using the proper method if we touch outside the &lt;em&gt;EditText&lt;/em&gt; area (if the focus is not anymore on it).&lt;/p&gt;&lt;p&gt;Afterwards, create a layout similar to this&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;LinearLayout
    xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot; 
    android:layout_width=&amp;quot;match_parent&amp;quot;
    android:layout_height=&amp;quot;match_parent&amp;quot;
    android:clickable=&amp;quot;true&amp;quot;
    android:focusableInTouchMode=&amp;quot;true&amp;quot;
    android:orientation=&amp;quot;vertical&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;being sure, whatever layout you want to use, to add &lt;em&gt;clickable&lt;/em&gt; and &lt;em&gt;focusableInTouchMode&lt;/em&gt; attributes with &lt;em&gt;true&lt;/em&gt; value.&lt;/p&gt;&lt;p&gt;Now just add our &lt;em&gt;AutoHideKeyboardTextView&lt;/em&gt; inside that layout. You can simply add it using XML like the following&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;com.w3ma.androidshowcase.autohidekeyboard.AutoHideKeyboardEditText 
    android:layout_width=&amp;quot;match_parent&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You are done! Whenever you touch outside the &lt;em&gt;EditText&lt;/em&gt; the keyboard will be automatically dismissed!&lt;/p&gt;&lt;p&gt;Please note: you should add the &lt;em&gt;clickable&lt;/em&gt; and &lt;em&gt;focusableInTouchMode&lt;/em&gt; attributes to the outermost view but if it is a &lt;em&gt;ScrollView&lt;/em&gt; this might not work. For such case, those attributes could be added to the view directly under the &lt;em&gt;ScrollView&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Full source code on &lt;a href=&quot;/w3ma-github-repository-showcase/&quot;&gt;my GitHub repository here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Source: &lt;a href=&quot;https://stackoverflow.com/questions/4165414/how-to-hide-soft-keyboard-on-android-after-clicking-outside-edittext/19828165#19828165&quot;&gt;https://stackoverflow.com/questions/4165414/how-to-hide-soft-keyboard-on-android-after-clicking-outside-edittext/19828165#19828165&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I don’t know why this wasn’t voted as the best answer because it is indeed.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Android Studio gitignore to avoid useless files]]></title><description><![CDATA[Create a perfect Android Studio  .gitignore  file is sometimes tedious, so I have gathered some information on the Internet, mainly on…]]></description><link>https://www.emanuelepapa.dev/android-studio-gitignore-to-avoid-useless-files/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/android-studio-gitignore-to-avoid-useless-files/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Fri, 20 Nov 2015 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/eb082efe94f0fa30b6727a83dea8ace6/Gitignore_Screenshot.png" length="148245" type="image/png"/><content:encoded>&lt;p&gt;Create a perfect Android Studio &lt;em&gt;.gitignore&lt;/em&gt; file is sometimes tedious, so I have gathered some information on the Internet, mainly on &lt;a href=&quot;https://stackoverflow.com/&quot;&gt;StackOverflow&lt;/a&gt;, I made some experiments with my sample project over and over and finally I obtained a perfect Android Studio .gitignore file.&lt;/p&gt;&lt;p&gt;Usually, I create my project inside a parent folder which I initialize as my Git repository.&lt;/p&gt;&lt;p&gt;So, for example, this is a sample structure (&lt;strong&gt;folders are bolded&lt;/strong&gt;):&lt;/p&gt;&lt;p&gt;&lt;strong&gt;AndroidShowcaseRepository&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;.git&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;AndroidShowCase&lt;/strong&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;.gradle&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;.idea&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;app&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;build&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;gradle&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;.gitignore&lt;/li&gt;&lt;li&gt;AndroidShowcase.iml&lt;/li&gt;&lt;li&gt;build.gradle&lt;/li&gt;&lt;li&gt;gradlew&lt;/li&gt;&lt;li&gt;gradlew.bat&lt;/li&gt;&lt;li&gt;local.properties&lt;/li&gt;&lt;li&gt;settings.gradle&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;.gitignore&lt;/li&gt;&lt;li&gt;LICENSE&lt;/li&gt;&lt;li&gt;README.md&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I use the following as content for the &lt;em&gt;.gitignore&lt;/em&gt; file in the &lt;em&gt;AndroidShowcaseRepository&lt;/em&gt; folder&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# Built application files 
*.apk
*.ap_

# Files for the Dalvik VM
*.dex

# Java class files
*.class

# Generated files 
bin/
gen/

# Gradle files
.gradle/
build/
/*/build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and the following as content for the &lt;em&gt;.gitignore&lt;/em&gt; file in the &lt;em&gt;AndroidShowcase&lt;/em&gt; folder&lt;/p&gt;&lt;pre&gt;&lt;code&gt;.gradle
/local.properties
.DS_Store
/build
/captures
.idea
gradlew
gradlew.bat
gradle
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using these two .gitignore files (and the default ones created by Android Studio inside the &lt;em&gt;app&lt;/em&gt; folder and into other folders you could have) you will likely save only the necessary source code to your repository and not files which could be generated by Android Studio itself.&lt;/p&gt;&lt;p&gt;Check &lt;a href=&quot;/w3ma-github-repository-showcase/&quot;&gt;w3ma showcase repository&lt;/a&gt; for the complete source code and for more explanation!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Making TextView with clickable link in Android]]></title><description><![CDATA[I will explain how to create a TextView with inside a clickable link in Android. Do you want to open a static link without doing other…]]></description><link>https://www.emanuelepapa.dev/making-textview-with-clickable-link-in-android/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/making-textview-with-clickable-link-in-android/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Thu, 19 Nov 2015 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/de864fbf4f5bcce41ee12b80ef766097/connection-481995_1920.jpeg" length="425324" type="image/jpeg"/><content:encoded>&lt;p&gt;I will explain how to create a TextView with inside a clickable link in Android.&lt;/p&gt;&lt;p&gt;Do you want to open a static link without doing other operations? So just follow the first part of this tutorial! Otherwise, if you want to have more control on the link customization and operations after the user taps on it, follow the second part of the tutorial.&lt;/p&gt;&lt;h2 id=&quot;static-clickable-link-inside-stringsxml-resource&quot;&gt;Static clickable link inside strings.xml resource&lt;/h2&gt;&lt;p&gt;Let’s create an example TextView in your layout like this one:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;TextView 
    android:id=&amp;quot;@+id/messageWithLinkTextView&amp;quot;
    android:layout_width=&amp;quot;wrap_content&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:layout_centerInParent=&amp;quot;true&amp;quot;
    android:gravity=&amp;quot;center&amp;quot;
    android:text=&amp;quot;@string/messageWithLink&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Create this string in &lt;code&gt;strings.xml&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;string name=&amp;quot;messageWithLink&amp;quot;&amp;gt;This is a message with a link inside!\n&amp;lt;a href=&amp;quot;https://www.google.com/&amp;quot;&amp;gt;Tap here to open it!&amp;lt;/a&amp;gt;&amp;lt;/string&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Write this code in your activity/fragment:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bind(R.id.messageWithLinkTextView) 
TextView messageWithLinkTextView; 

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState); 
    ButterKnife.bind(this, view); 
    messageWithLinkTextView.setMovementMethod(LinkMovementMethod.getInstance());
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Please note: I’m using &lt;a href=&quot;https://jakewharton.github.io/butterknife/&quot;&gt;ButterKnife&lt;/a&gt; library to bind fields and views (I suggest you to have a look at this fantastic library!)&lt;/p&gt;&lt;p&gt;You are done! You should get a TextView like the one in the following screenshot and when you tap on the hyperlink the browser should open! Cool!&lt;/p&gt;&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1140px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/addc37919c397d6f08ba6f2f2dd2988a/07a9c/textview_with_link.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:177.89473684210526%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAIAAAAGkY33AAAACXBIWXMAAAsTAAALEwEAmpwYAAAB00lEQVR42mMwsJ8PR4YOUARk69shuGC0QNNyTlTaxtjMLVFpm8KSN3iGrWKw9llv47seSFp6r7P0Wmfmsc7ccy2QYeW93sxjrbnHWlN3ENcWrMY+YINT8Cbn4E2OQZscAjcxBCbtLao/WdF2OrXkSFLh4Zyq47nVIFTWfCq97ChQMLfqWHDKPkvfrXaB22z8t9n4bbX222oDRgw+MduDEncGJ+8MTNoZmLgzLHVXaMouIDckZVdIys6Q5F3h6bv943e4R2zxiNwKRttg5DaG6zcfXrvxAI6uXgchOAPOvXHrISZi+PjxPdmI4Q0FgOEtBWBU86hm0jQDU9zr16+B5Lu3byEIKARBcCnsmt+9e/f06dObN2/du3f/8s2bF65ePXflypWbNy7fAKE7d+9ev3798ePHQGU4nQ2We3fqwf2j9++cvH/39EMQ4/yjB0BLkbXh8/OHd+/ev3uHTJIQYG8w0GgiGdVMpuZXFACG30jgz58/v/ECNAUMU2Bg8uTJEyZMmIIEJoEBsghQAVAZnMsAB8zMzDIyMoyMjEA2hBQEAiEhOBcIgArY2dkRepjAACjNwsIiJiYGNAIiApQSAAO4GiCQlJTk4OAAmsbCxswtyAYAS33UITEyDisAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;TextView with link&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/addc37919c397d6f08ba6f2f2dd2988a/b5cea/textview_with_link.png&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/addc37919c397d6f08ba6f2f2dd2988a/0e2fe/textview_with_link.png 285w,/static/addc37919c397d6f08ba6f2f2dd2988a/432e7/textview_with_link.png 570w,/static/addc37919c397d6f08ba6f2f2dd2988a/b5cea/textview_with_link.png 1140w,/static/addc37919c397d6f08ba6f2f2dd2988a/07a9c/textview_with_link.png 1440w&quot; sizes=&quot;(max-width: 1140px) 100vw, 1140px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;TextView with link&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;&lt;p&gt;P.S. unfortunately I didn’t find a way of achieving this only using XML properties. If anyone knows how to do it, please let me know in the comments!&lt;/p&gt;&lt;h2 id=&quot;custom-clickable-link-and-custom-actions&quot;&gt;Custom clickable link and custom actions&lt;/h2&gt;&lt;p&gt;Create another TextView in your layout:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;TextView
   android:id=&amp;quot;@+id/messageWithSpannableLinkTextView&amp;quot;
   android:layout_width=&amp;quot;wrap_content&amp;quot;
   android:layout_height=&amp;quot;wrap_content&amp;quot;
   android:layout_centerInParent=&amp;quot;true&amp;quot;
   android:gravity=&amp;quot;center&amp;quot;
   tools:text=&amp;quot;@string/messageWithSpannableLink&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Create another string resource in &lt;code&gt;strings.xml&lt;/code&gt;:&lt;/p&gt;&lt;p&gt;&lt;code&gt;&amp;lt;string name=&amp;quot;messageWithSpannableLink&amp;quot;&amp;gt;This is a message with a spannable link inside!\nTap here to open it!&amp;lt;/string&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Please note: this time there is no &lt;em&gt;&lt;a&gt;&lt;/a&gt;&lt;/em&gt; tag inside the string!&lt;/p&gt;&lt;p&gt;Now write this code in your activity/fragment:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bind(R.id.messageWithSpannableLinkTextView)
TextView messageWithSpannableLinkTextView;

@Override
public void onViewCreated(View view, Bundle savedInstanceState) { 
    super.onViewCreated(view, savedInstanceState);
    ButterKnife.bind(this, view);
    SpannableString spannableString = new SpannableString(getString(R.string.messageWithSpannableLink));
    ClickableSpan clickableSpan = new ClickableSpan() { 
        @Override 
        public void onClick(View textView) { 
            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(&amp;quot;https://www.google.com&amp;quot;))); 
        } 
    }; 
    spannableString.setSpan(clickableSpan, spannableString.length() - 20, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    messageWithSpannableLinkTextView.setText(spannableString, TextView.BufferType.SPANNABLE);
    messageWithSpannableLinkTextView.setMovementMethod(LinkMovementMethod.getInstance());
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You are done! You should get a TextView like the one in the following screenshot and when you tap on the hyperlink the ClickableSpan-&amp;gt;onClick() method should be called, so you can do your operations and let the browser open the link! Cool!&lt;/p&gt;&lt;p&gt;&lt;figure class=&quot;gatsby-resp-image-figure&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:auto;margin-right:auto;max-width:1140px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;https://www.emanuelepapa.dev/static/fef90f55b0a440ab541c697fe0f0efad/07a9c/textview_with_spannable_link.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:177.89473684210526%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAIAAAAGkY33AAAACXBIWXMAAAsTAAALEwEAmpwYAAAB3UlEQVR42mMwsJ8PR4YOUARk69shuGC0QNNyTlTaxtjMLVFpm8KSN3iGrWKw9llv47seSFp6r7P0Wmfmsc7ccy2QYeW93sxjrbnHWlN3ENcWrMY+YINT0Cbn4E2OQZscAjcxBCbtLao/WdF2OrXkSFLh4Zyq47nVIFTWfCq97ChQMKfqWHDKPkvfrXaB22z8t9n4bbX222oDRgw+MduDknYGJ+8MTAShsLRdoam7gNyQlF0hKSAyPH23f/wO94gtHpFbwWgbjNzGcOPmw+s3Hly78QBCXrsOJpHR9QfXbz64eeshJmL4+PE92YjhDQWA4S0FYFTzqGZiNQPT2uvXr+Hp7t3btygISRbIQNH87t27V69e3b59++7de0B069btM5cuX7px48rNm5dv3Lx47dr5K1eu37hxBwyuXbsG1A/UguLsd2AAYZx6cO/YvTtA8jSYcf7RgzcwBRA1+Pz84d07IHoPRhAGCQH2BgONJpJRzWRqfkUBYPiNBP78+fMbL0BTwDAFBiZPnjxhwoQpSGASGCCLABUAlcG5DHDAzMwsIyPDyMgIZENIQSAQEoJzgQCogJ2dHaGHCQyA0iwsLGLiYkAjICJAKQEwgKsBAgkJSQ4ODqBpLGzM3IJsAOOX03ugIlwTAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;TextView with spannable link inside&quot; title=&quot;&quot; src=&quot;https://www.emanuelepapa.dev/static/fef90f55b0a440ab541c697fe0f0efad/b5cea/textview_with_spannable_link.png&quot; srcSet=&quot;https://www.emanuelepapa.dev/static/fef90f55b0a440ab541c697fe0f0efad/0e2fe/textview_with_spannable_link.png 285w,/static/fef90f55b0a440ab541c697fe0f0efad/432e7/textview_with_spannable_link.png 570w,/static/fef90f55b0a440ab541c697fe0f0efad/b5cea/textview_with_spannable_link.png 1140w,/static/fef90f55b0a440ab541c697fe0f0efad/07a9c/textview_with_spannable_link.png 1440w&quot; sizes=&quot;(max-width: 1140px) 100vw, 1140px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;
    &lt;figcaption class=&quot;gatsby-resp-image-figcaption&quot;&gt;TextView with spannable link inside&lt;/figcaption&gt;
  &lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Check out the complete source code on &lt;a href=&quot;https://github.com/ema987/androidshowcase&quot;&gt;my GitHub here!&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[W3MA GitHub Repository Showcase]]></title><description><![CDATA[Hi guys, this post is just to show you a link to the w3ma github repository showcase I will use to share the Android code I will post in the…]]></description><link>https://www.emanuelepapa.dev/w3ma-github-repository-showcase/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/w3ma-github-repository-showcase/</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Wed, 18 Nov 2015 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/4e5880feebed6b820bd24c4acd228fd1/pexels-cleyder-duque-3821385.jpeg" length="5958260" type="image/jpeg"/><content:encoded>&lt;p&gt;Hi guys, this post is just to show you a link to the w3ma github repository showcase I will use to share the Android code I will post in the future in the blog.&lt;/p&gt;&lt;p&gt;It is the best way to always show a fully compiling code to everyone who wants to have a look at it.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/ema987/androidshowcase&quot;&gt;Just click here to see it!&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I hope you will enjoy my code.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Assign static device name using udev rules]]></title><description><![CDATA[How can you assign static device name using udev rules to a USB device? Why do you need it? Imagine you made a script which refers to a…]]></description><link>https://www.emanuelepapa.dev/assign-static-device-name-using-udev-rules/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/assign-static-device-name-using-udev-rules/</guid><category><![CDATA[Linux]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sat, 24 Oct 2015 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/fdc6a585592b99f3483229b1481a07f1/pexels-heiner-204997.jpeg" length="3252063" type="image/jpeg"/><content:encoded>&lt;p&gt;How can you assign static device name using udev rules to a USB device? Why do you need it?&lt;/p&gt;&lt;p&gt;Imagine you made a script which refers to a particular device you could plugin trough USB port: sometimes this device could be mounted as &lt;em&gt;/dev/sdb1&lt;/em&gt;, sometimes as &lt;em&gt;/dev/sdc1&lt;/em&gt;, and so on. You may need a static reference to it because you didn’t know which name it will use.&lt;/p&gt;&lt;p&gt;Another scenario could be a startup mount of a drive as discussed in &lt;a href=&quot;../mount-ntfs-partition-at-startup&quot;&gt;How to mount ntfs partition at startup&lt;/a&gt;&lt;/p&gt;&lt;p&gt;To achieve this, let first discover some attributes of our device using the command&lt;/p&gt;&lt;p&gt;&lt;code&gt;sudo lsusb&lt;/code&gt;&lt;/p&gt;&lt;p&gt;We will receive an output like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;emanuele@ubuntu:~$ lsusb
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 008 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 004: ID 1058:107c Western Digital Technologies, Inc.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Have a look at the &lt;em&gt;Western Digital Technologies&lt;/em&gt; row, this is the device we are looking for: &lt;em&gt;1058&lt;/em&gt; is the &lt;em&gt;Vendor ID&lt;/em&gt; and &lt;em&gt;107c&lt;/em&gt; is the &lt;em&gt;Product ID&lt;/em&gt;! We will use these information to uniquely identify our device.&lt;/p&gt;&lt;p&gt;Now, let’s create a new udev rule typing&lt;/p&gt;&lt;p&gt;&lt;code&gt;sudo nano /etc/udev/rules.d/99-my_rules.rules&lt;/code&gt;&lt;/p&gt;&lt;p&gt;and writing the following into the file&lt;/p&gt;&lt;p&gt;&lt;code&gt;ACTION==&amp;quot;add&amp;quot;, ATTRS{idVendor}==&amp;quot;1058&amp;quot;, ATTRS{idProduct}==&amp;quot;107c&amp;quot;, SYMLINK+=&amp;quot;my_wd_usb_hd&amp;quot;&lt;/code&gt;&lt;/p&gt;&lt;p&gt;where the &lt;em&gt;ATTRS&lt;/em&gt; numbers are the ones we discovered before, while &lt;em&gt;my_wd_usb_hd&lt;/em&gt; is the name we choose for our device.&lt;/p&gt;&lt;p&gt;Now let’s reboot our pc! Everytime it reboots this rule will be applied and we will always find our device under &lt;em&gt;/dev/my_wd_usb_hd&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Mount NTFS partition at startup]]></title><description><![CDATA[In this post we will learn how to mount NTFS partition at startup in a very simple way. You can use the following command to retrieve a list…]]></description><link>https://www.emanuelepapa.dev/mount-ntfs-partition-at-startup/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/mount-ntfs-partition-at-startup/</guid><category><![CDATA[Linux]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Thu, 22 Oct 2015 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/50dc7ad37132b3c540df0b1c203c1a4b/pexels-azamat-esenaliev-117729.jpeg" length="1659989" type="image/jpeg"/><content:encoded>&lt;p&gt;In this post we will learn how to mount NTFS partition at startup in a very simple way.&lt;/p&gt;&lt;p&gt;You can use the following command to retrieve a list of your partitions and their &lt;a href=&quot;https://en.wikipedia.org/wiki/Universally_unique_identifier&quot;&gt;UUID&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;code&gt;ls -l /dev/disk/by-uuid&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Pick the one you want to automatically mount and open the file &lt;code&gt;/etc/fstab&lt;/code&gt; by using&lt;/p&gt;&lt;p&gt;&lt;code&gt;sudo nano /etc/fstab&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Add a new row like:&lt;/p&gt;&lt;p&gt;&lt;code&gt;UUID=XXXXXXXXXXX  /hd_mount_point       ntfs-3g defaults        0       0&lt;/code&gt;&lt;/p&gt;&lt;p&gt;where &lt;em&gt;XXXXXXXXXXX&lt;/em&gt; is your partition &lt;em&gt;UUID&lt;/em&gt; and &lt;em&gt;/hd_mount_point&lt;/em&gt; is a folder on your disk you will use to access that partition.&lt;/p&gt;&lt;p&gt;Simply reboot and you are done!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Force Ubuntu boot after power failure]]></title><description><![CDATA[Hello, in this blog post I will show a little trick to avoid your Ubuntu installation got stuck on GRUB boot menu waiting for you to choose…]]></description><link>https://www.emanuelepapa.dev/force-ubuntu-boot-after-power-failure/</link><guid isPermaLink="false">https://www.emanuelepapa.dev/force-ubuntu-boot-after-power-failure/</guid><category><![CDATA[Linux]]></category><dc:creator><![CDATA[Emanuele Papa]]></dc:creator><pubDate>Sat, 10 Oct 2015 00:00:00 GMT</pubDate><enclosure url="https://www.emanuelepapa.dev/static/f53c05dee6acc131910dacf8bd39650d/pexels-wilson-vitorino-2177473.jpeg" length="1089137" type="image/jpeg"/><content:encoded>&lt;p&gt;Hello,&lt;/p&gt;&lt;p&gt;in this blog post I will show a little trick to avoid your Ubuntu installation got stuck on GRUB boot menu waiting for you to choose an option after a power failure.&lt;/p&gt;&lt;p&gt;To force Ubuntu boot you can edit your &lt;code&gt;/etc/default/grub&lt;/code&gt; with your favourite text editor, e.g. running this command from your terminal:&lt;/p&gt;&lt;p&gt;&lt;code&gt;nano /etc/default/grub&lt;/code&gt;&lt;/p&gt;&lt;p&gt;and then add this line in the file:&lt;/p&gt;&lt;p&gt;&lt;code&gt;GRUB_RECORDFAIL_TIMEOUT=0&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Afterwards, remember to run:&lt;/p&gt;&lt;p&gt;&lt;code&gt;sudo update-grub&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Next time your pc will have a power failure it will reboot without getting stuck on GRUB!&lt;/p&gt;</content:encoded></item></channel></rss>