<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://dmitrysamoylenko.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://dmitrysamoylenko.com/" rel="alternate" type="text/html" /><updated>2026-06-16T07:27:40+00:00</updated><id>https://dmitrysamoylenko.com/feed.xml</id><title type="html">Android Developer Blog</title><subtitle>This is a personal blog. Mail me if you want to use materials from it.</subtitle><author><name>Dmitry Samoylenko</name></author><entry><title type="html">How to debug Android apps over the Internet</title><link href="https://dmitrysamoylenko.com/2024/06/26/android-adb-debug-over-internet.html" rel="alternate" type="text/html" title="How to debug Android apps over the Internet" /><published>2024-06-26T00:00:00+00:00</published><updated>2024-06-26T00:00:00+00:00</updated><id>https://dmitrysamoylenko.com/2024/06/26/android-adb-debug-over-internet</id><content type="html" xml:base="https://dmitrysamoylenko.com/2024/06/26/android-adb-debug-over-internet.html"><![CDATA[<h1 id="how-to-debug-android-apps-over-the-internet">How to debug Android apps over the Internet</h1>

<h2 id="the-problem">The problem</h2>

<p>You working remotely and you and the target device are too far away. Or you work on a remote server and want to debug your app on your phone.</p>

<h2 id="the-solution">The solution</h2>

<ol>
  <li>Connect phone to the one machine with adb, let’s call it “machine A</li>
  <li>On the machine A, run <code class="language-plaintext highlighter-rouge">adb kill-server</code> and <code class="language-plaintext highlighter-rouge">adb start-server</code></li>
  <li>On the machine A, run <code class="language-plaintext highlighter-rouge">ssh -R 5555:localhost:5555 machineB</code></li>
  <li>On the remote machine, where you want to debug, run <code class="language-plaintext highlighter-rouge">adb devices</code> to check that the phone is connected</li>
</ol>]]></content><author><name>Dmitry Samoylenko</name></author><summary type="html"><![CDATA[How to debug Android apps over the Internet]]></summary></entry><entry><title type="html">How to cast Linux audio and microphone to Discord</title><link href="https://dmitrysamoylenko.com/2023/11/25/linux-music-and-mic-to-discord.html" rel="alternate" type="text/html" title="How to cast Linux audio and microphone to Discord" /><published>2023-11-25T00:00:00+00:00</published><updated>2023-11-25T00:00:00+00:00</updated><id>https://dmitrysamoylenko.com/2023/11/25/linux-music-and-mic-to-discord</id><content type="html" xml:base="https://dmitrysamoylenko.com/2023/11/25/linux-music-and-mic-to-discord.html"><![CDATA[<h1 id="how-to-cast-linux-audio-and-microphone-to-discord">How to cast Linux audio and microphone to Discord</h1>

<h2 id="the-problem">The problem</h2>

<p>Discord on Linux doesn’t have the option by defaul to play audio from you apps simultaneously with your microphone.</p>

<h2 id="the-solution">The solution</h2>

<p>What we do is to create two virtual sinks, one for microphone and another for music.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
pactl load-module module-null-sink <span class="nv">sink_name</span><span class="o">=</span>MicSink <span class="nv">sink_properties</span><span class="o">=</span>device.description<span class="o">=</span>MicSink
pactl load-module module-null-sink <span class="nv">sink_name</span><span class="o">=</span>MusicSink <span class="nv">sink_properties</span><span class="o">=</span>device.description<span class="o">=</span>MusicSink

</code></pre></div></div>

<p>Thats just two empty devices, let’s bring them to life.
We want to play music in our output device (in my case it is called “TU106”)</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
pactl load-module module-loopback <span class="nb">source</span><span class="o">=</span><span class="s2">"MusicSink.monitor"</span> <span class="nv">sink</span><span class="o">=</span><span class="s2">"TU106"</span>

</code></pre></div></div>

<p>And we also want to send the same audio to the microphone sink, as it will go to Discord.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
pactl load-module module-loopback <span class="nb">source</span><span class="o">=</span><span class="s2">"MusicSink.monitor"</span> <span class="nv">sink</span><span class="o">=</span><span class="s2">"MicSink"</span>

</code></pre></div></div>

<p>The final step is to send your physical microphone to the virtual microphone sink. (in my case mic called “NoiseTorch Mic…”)</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
pactl load-module module-loopback <span class="nb">source</span><span class="o">=</span><span class="s2">"NoiseTorch Microphone for THRONMAX PULSE MICROPHONE"</span> <span class="nv">sink</span><span class="o">=</span>MicSink

</code></pre></div></div>

<h2 id="the-final-setup">The final setup</h2>

<p>Now, go to pulseaudio pavucontrol and in Playback select “MusicSink” for your music player, and in Recording select “MicSink” for Discord. One of the “loopback-…” devices must play in your physical output device, others go to MicSink.</p>]]></content><author><name>Dmitry Samoylenko</name></author><summary type="html"><![CDATA[How to cast Linux audio and microphone to Discord]]></summary></entry><entry><title type="html">Databinding Episode II; Past Mistakes</title><link href="https://dmitrysamoylenko.com/2021/10/13/databinding_past_mistakes.html" rel="alternate" type="text/html" title="Databinding Episode II; Past Mistakes" /><published>2021-10-13T00:00:00+00:00</published><updated>2021-10-13T00:00:00+00:00</updated><id>https://dmitrysamoylenko.com/2021/10/13/databinding_past_mistakes</id><content type="html" xml:base="https://dmitrysamoylenko.com/2021/10/13/databinding_past_mistakes.html"><![CDATA[<h1 id="databinding-episode-ii-mistakes-of-the-past">Databinding Episode II; Mistakes of the Past</h1>

<p>Sometimes in everyday Android development, you encounter strange, if not mystical bugs. (standard introduction)</p>

<p>In the previous episode <a href="http://dmitrysamoylenko.com/2019/04/16/databinding_hidden_danger.html">Databinding Episode I; Hidden Danger</a></p>

<p>Let’s assume there is a *.kt class:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Base.kt
open class Base {
  open fun isEmpty() = false
}
</code></pre></div></div>

<p>and its *.java inheritor:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Child.java
class Child extends Base {
  public boolean isEmpty = true;
}
</code></pre></div></div>

<p>Here we can already notice that we have shot ourselves in the foot. But let’s continue.</p>

<p>We wanted to use the Child class in databinding.</p>

<p>Attention, question: will the following View be visible?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>some.xml
&lt;layout&gt;
&lt;data&gt;
  &lt;variable name="child" type="Child" /&gt;
&lt;/data&gt;

&lt;View
  ...
  android:visibility="@{child.isEmpty ? View.VISIBLE : View.GONE}"
  ...
</code></pre></div></div>

<h1 id="moral-of-the-story">Moral of the Story</h1>

<ol>
  <li>Do not inherit Java classes from Kotlin classes.</li>
  <li>Maybe it’s time to switch to Jetpack Compose?</li>
</ol>]]></content><author><name>Dmitry Samoylenko</name></author><summary type="html"><![CDATA[Databinding Episode II; Mistakes of the Past]]></summary></entry><entry><title type="html">Finishing the Thiсс-15</title><link href="https://dmitrysamoylenko.com/2021/04/01/choose-right-tool.html" rel="alternate" type="text/html" title="Finishing the Thiсс-15" /><published>2021-04-01T00:00:00+00:00</published><updated>2021-04-01T00:00:00+00:00</updated><id>https://dmitrysamoylenko.com/2021/04/01/choose-right-tool</id><content type="html" xml:base="https://dmitrysamoylenko.com/2021/04/01/choose-right-tool.html"><![CDATA[<h1 id="or-a-laptop-for-android-development">Or a Laptop for Android Development</h1>
<p>First step - we buy <a href="https://www.eluktronics.com/THICC15-BYO">this chunky guy</a> Update: on their site, only the full version with internals <a href="https://www.eluktronics.com/THICC-15/">THICC-15</a> is left</p>

<p>What is it: an empty laptop without a processor, RAM, hard drive.</p>

<p>Why choose it?</p>

<p>Firstly, without these components, the duty will be less. Secondly, it’s the only one of its kind (apart from its twins, like Xmg Apex-15) laptop supporting a desktop Ryzen 9.</p>

<p>While the package is on its way, we quickly buy ourselves a Ryzen 3950x (because an Android project is serious business)</p>

<p><img src="https://dmitrysamoylenko.com/assets/ryzen.jpg" alt="ryzen" /></p>

<p>Ordered, received, unpacked, happy?</p>

<p><img src="https://dmitrysamoylenko.com/assets/delivery.jpg" alt="delivery" /></p>

<p>Unfortunately, it’s slightly too noisy even without heavy load. 16 cores after all.</p>

<p><img src="https://dmitrysamoylenko.com/assets/inside.jpg" alt="inside" />
Thinking about what to do with it.</p>

<h1 id="finishing">Finishing</h1>

<p>Literally. We take a jigsaw, remove the cover and carefully cut out the middle of it. O_O</p>

<p><img src="https://dmitrysamoylenko.com/assets/saw1.jpg" alt="saw1" /></p>

<p>No turning back now :)</p>

<p><img src="https://dmitrysamoylenko.com/assets/saw2.jpg" alt="saw2" /></p>

<p>Now we need a mesh. We cut it out of an old laptop stand.</p>

<p><img src="https://dmitrysamoylenko.com/assets/net.jpg" alt="net" /></p>

<p>Glue the mesh to the frame of the cover with epoxy resin and super glue.</p>

<p><img src="https://dmitrysamoylenko.com/assets/back.jpg" alt="back" /></p>

<p>Almost ready.</p>

<p>We buy small copper plates for cooling separately. Height ~3.5 mm.</p>

<p><img src="https://dmitrysamoylenko.com/assets/cu.jpg" alt="cu" /></p>

<p>We will attach them on top of the cooling system, so we need to remove the labels-handles from it and clean the surface.</p>

<p>Copper wire and heat-resistant rubber rings will be suitable for attachment.</p>

<p><img src="https://dmitrysamoylenko.com/assets/circles.jpg" alt="circles" /></p>

<p><img src="https://dmitrysamoylenko.com/assets/install.jpg" alt="install" /></p>

<p>Don’t forget to apply thermal paste, as the surface is uneven.</p>

<p><img src="https://dmitrysamoylenko.com/assets/installed1.jpg" alt="installed1" /></p>

<p><img src="https://dmitrysamoylenko.com/assets/installed2.jpg" alt="installed2" /></p>

<p>All done, ready to assemble.</p>

<p><img src="https://dmitrysamoylenko.com/assets/ready1.jpg" alt="ready1" /></p>

<p><img src="https://dmitrysamoylenko.com/assets/ready2.jpg" alt="ready2" /></p>

<p><img src="https://dmitrysamoylenko.com/assets/under.jpg" alt="under" /></p>

<h1 id="why-such-power">Why Such Power?</h1>

<p>The laptop is suitable as a portable desktop computer. It’s convenient when your entire environment is always with you in full power.</p>

<p><img src="https://dmitrysamoylenko.com/assets/final.jpg" alt="final" /></p>]]></content><author><name>Dmitry Samoylenko</name></author><summary type="html"><![CDATA[Or a Laptop for Android Development First step - we buy this chunky guy Update: on their site, only the full version with internals THICC-15 is left]]></summary></entry><entry><title type="html">Experiment with Kotlin Mobile Multiplatform</title><link href="https://dmitrysamoylenko.com/2021/01/22/kmm.html" rel="alternate" type="text/html" title="Experiment with Kotlin Mobile Multiplatform" /><published>2021-01-22T00:00:00+00:00</published><updated>2021-01-22T00:00:00+00:00</updated><id>https://dmitrysamoylenko.com/2021/01/22/kmm</id><content type="html" xml:base="https://dmitrysamoylenko.com/2021/01/22/kmm.html"><![CDATA[<h1 id="experiment-with-ivi-pages">Experiment with ivi pages:</h1>
<p><img src="https://dmitrysamoylenko.com/assets/android_and_ios.png" alt="android_and_ios" /></p>

<p>Recording of this talk - <a href="https://www.twitch.tv/videos/883386268">Experiment with Kotlin Mobile Mutliplatform</a></p>

<ul>
  <li>repository https://github.com/samoylenkodmitry/KMMSimple</li>
  <li>threading - on iOS there are no coroutines, you need to invent an adapter</li>
  <li>iOS shared file is too large - 
2MB minimum,</li>
</ul>

<p><img src="https://dmitrysamoylenko.com/assets/size_min.png" alt="size_min" /></p>

<p>+9 MB for ktor,</p>

<p><img src="https://dmitrysamoylenko.com/assets/size_ktor.png" alt="size_ktor" /></p>

<p>+2MB for Kotlin serialization</p>

<p><img src="https://dmitrysamoylenko.com/assets/size_ktor_and_serializable.png" alt="size_ktor_and_serializable" /></p>

<p>Comparison of the final app size for iOS vs Android:
<img src="https://dmitrysamoylenko.com/assets/app_size_comarsion.png" alt="app_size_comarsion.png" /></p>

<h1 id="pros">Pros:</h1>

<ul>
  <li>shared code</li>
  <li>for Android, there is no difference, you can write for multiplatform “just in case”</li>
</ul>

<h1 id="challenges">Challenges:</h1>

<ul>
  <li>need to maintain the library, both platforms will use it</li>
  <li>cannot break the compatibility of the library</li>
  <li>set up CI/CD</li>
  <li>unit tests</li>
  <li>all iOS developers need to install gradle, java, set up the environment</li>
  <li>problem with the large size of the shared file on iOS</li>
  <li>Android developer will need a Mac</li>
</ul>

<h1 id="predictions">Predictions:</h1>
<ul>
  <li>a new type of job on the market - a multiplatform programmer</li>
  <li>competition among native programmers will be higher, harder to find a job</li>
  <li>there will be a future simply because it is profitable for business (unless Apple interferes)</li>
</ul>

<h1 id="experience-of-others">Experience of others:</h1>

<p>A good talk-interview on the topic:
<a href="https://youtu.be/LRhNgv2EgSw">Chasing two rabbits: is it necessary to be able to develop for Android and iOS Alexey Gladkov. Technical architect at Leroy Merlin</a></p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/LRhNgv2EgSw" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>]]></content><author><name>Dmitry Samoylenko</name></author><summary type="html"><![CDATA[Experiment with ivi pages:]]></summary></entry><entry><title type="html">Setting up precommit lints for all team members</title><link href="https://dmitrysamoylenko.com/2020/12/06/configure_lints.html" rel="alternate" type="text/html" title="Setting up precommit lints for all team members" /><published>2020-12-06T00:00:00+00:00</published><updated>2020-12-06T00:00:00+00:00</updated><id>https://dmitrysamoylenko.com/2020/12/06/configure_lints</id><content type="html" xml:base="https://dmitrysamoylenko.com/2020/12/06/configure_lints.html"><![CDATA[<h1 id="what-lints">What lints?</h1>

<p>Current actively developed lints are Detekt and ktlint. klint have a <code class="language-plaintext highlighter-rouge">don't overengineer</code> philosophy while detekt is more configurable.</p>

<h1 id="how-to-set-up-detekt">How to set up Detekt?</h1>

<p><a href="https://github.com/detekt/detekt">https://github.com/detekt/detekt</a></p>

<p>Download binary from here <a href="https://github.com/detekt/detekt/releases">https://github.com/detekt/detekt/releases</a>
Config file can be auto generated from command line or simple download mine <a href="https://gist.github.com/samoylenkodmitry/433572b16d22caa4a73d197ca92cbb69">default-detekt-config.yml</a></p>

<h1 id="how-to-set-up-ktlint">How to set up ktlint?</h1>

<p><a href="https://github.com/pinterest/ktlint">https://github.com/pinterest/ktlint</a>
Download binary from here <a href="https://github.com/pinterest/ktlint/releases">https://github.com/pinterest/ktlint/releases</a>
Ktlint can be configured with standard <code class="language-plaintext highlighter-rouge">.editorconfig</code> file. You can find one anywhere in github or get mine: 
<a href="https://gist.github.com/samoylenkodmitry/5b7bc43160e042f716460c1d9ba784ee">.editorconfig</a></p>

<h1 id="how-to-make-it-check-each-commit">How to make it check each commit?</h1>

<p>Copy and configure script from here: 
<a href="https://gist.github.com/samoylenkodmitry/0e988cd3445a0b390be20814eebce589">pre-commit</a></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash
# Setting up guide http://dmitrysamoylenko.com/2020/12/06/configure_lints.html
# https://github.com/checkstyle/checkstyle
# https://github.com/pinterest/ktlint
# https://github.com/detekt/detekt

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ]; then
  if [ -x "$JAVA_HOME/jre/sh/java" ]; then
    # IBM's JDK on AIX uses strange locations for the executables
    JAVACMD="$JAVA_HOME/jre/sh/java"
  else
    JAVACMD="$JAVA_HOME/bin/java"
  fi
  if [ ! -x "$JAVACMD" ]; then
    die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
  fi
else
  JAVACMD="java"
  which java &gt;/dev/null 2&gt;&amp;1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

GIT_ROOT_DIR=$(git rev-parse --show-toplevel)

cd ${GIT_ROOT_DIR}
#echo ${GIT_ROOT_DIR}
f=()
while read line; do
  if [ -n "$line" ]; then
    if [[ "$line" =~ .*"/build/".* ]]; then
      true #skip generated code
    else
      f+=("$line")
    fi
  fi
done &lt;&lt;&lt;"$(git diff --diff-filter=d --staged --name-only)"

filesJava=""
filesKt=""
countJava=0
countKt=0
for i in "${!f[@]}"; do
  if [[ "${f[i]}" == *.java ]]; then
    filesJava+=" ${f[i]}"
    countJava=$((countJava + 1))
  fi
  if [[ "${f[i]}" == *.kt ]]; then
    filesKt+=" ${f[i]}"
    countKt=$((countKt + 1))
  fi
done

for i in "${!f[@]}"; do
  if [[ "${f[i]}" =~ .*.(java|kt)$ ]]; then
    lineNum=0
    while IFS= read line; do
      if [ -n "$line" ]; then
        lineNum=$((lineNum+1))
        if [[ "$line" =~ ^\ +[^\*].* ]]; then
          echo "Line starts with spaces. Please apply project code style. File: ${f[i]}:$lineNum, line: $line"
          exit 1
        elif [[ "$line" =~ .*oleg.*|.*xoxoxo.* ]]; then
          echo "forbidden word in line. File: ${f[i]}:$lineNum, line: $line"
          exit 1
        else
          true #skip good code
        fi
      fi
    done &lt; "${f[i]}"
  fi
done

if [ ${#filesJava} -eq 0 ]; then
  echo "No *.java files to check."
else
  configloc=-Dconfig_loc=${GIT_ROOT_DIR}/ivi/config/checkstyle
  config=${GIT_ROOT_DIR}/ivi/config/checkstyle/checkstyle.xml
  params="${configloc} -jar ${GIT_ROOT_DIR}/githooks/checkstyle-8.38-all.jar -c ${config}${filesJava}"

  ${JAVACMD} $params
  result=$?
  if [ $result -ne 0 ]; then
    echo "Please fix the checkstyle problems before submit the commit!"
    exit $result
  else
    echo "#java files: $countJava"
  fi
fi

if [ ${#filesKt} -eq 0 ]; then
  echo "No *.kt files to check."
  exit 0
fi

# ktlint check
git diff --diff-filter=d --staged --name-only | grep '\.kt[s"]\?$' | xargs ./ktlint .
result=$?
if [ $result -ne 0 ]; then
  echo "Please fix the ktlint problems before submit the commit!"
  exit $result
fi

#detekt check

check_by_detekt() {
  arg=$1
  files=${arg%?}
  if [ ${#files} -eq 0 ]; then
    true #skip
  else
    params="--fail-fast --config default-detekt-config.yml --input ${files}"
    ./detekt ${params}
    result=$?
    if [ $result -ne 0 ]; then
      echo "Please fix the detekt problems before submit the commit!"
      exit $result
    fi
  fi
}
count=0
filesd=""
for i in "${!f[@]}"; do
  if [[ "${f[i]}" == *.kt ]]; then
    filesd+="${f[i]},"
    count=$((count + 1))
    # split into batches
    if [ $count -gt 1000 ]; then
      check_by_detekt $filesd
      count=0
      filesd=""
    fi
  fi
done

check_by_detekt $filesd
echo "# kt files: $count"
exit 0

</code></pre></div></div>

<h1 id="how-to-make-it-work-for-all-team-members">How to make it work for all team members?</h1>

<p>Make folder <code class="language-plaintext highlighter-rouge">githooks/</code> in the root git project directory and put all downloaded files into it.
Then in top of the project <code class="language-plaintext highlighter-rouge">build.gradle</code> insert:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exec {
	executable './../enable_lints.sh'
}
</code></pre></div></div>

<p>This will execute the script <code class="language-plaintext highlighter-rouge">enable_lints.sh</code> that will apply git path to the <code class="language-plaintext highlighter-rouge">githooks/</code> directory and make git execute the script <code class="language-plaintext highlighter-rouge">pre-commit</code> before each commit. Contents of the script:</p>

<p>enable_lints.sh</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash
git config --global core.hooksPath githooks
</code></pre></div></div>

<p>Remember to make each script executable</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x ./ktlint
chmod +x ./detekt
chmod +x ./githooks/pre-commit
</code></pre></div></div>
<p>Now add the <code class="language-plaintext highlighter-rouge">githook/</code> directory and all new files to git and push it to server. All team should just open project again so AndroidStudio will run <code class="language-plaintext highlighter-rouge">build.gradle</code> script.
Notice that script support only unix os and should be specially edited to support windows.</p>]]></content><author><name>Dmitry Samoylenko</name></author><summary type="html"><![CDATA[What lints?]]></summary></entry><entry><title type="html">Which is faster? ConcurrentSkipListSet vs TreeSet+synchronized</title><link href="https://dmitrysamoylenko.com/2020/10/05/concurrent_skip_list_set.html" rel="alternate" type="text/html" title="Which is faster? ConcurrentSkipListSet vs TreeSet+synchronized" /><published>2020-10-05T00:00:00+00:00</published><updated>2020-10-05T00:00:00+00:00</updated><id>https://dmitrysamoylenko.com/2020/10/05/concurrent_skip_list_set</id><content type="html" xml:base="https://dmitrysamoylenko.com/2020/10/05/concurrent_skip_list_set.html"><![CDATA[<h1 id="whats-the-difference">What’s the difference</h1>
<p>Those two collections are all sorted and very useful when you need to maintain some order in dynamically filled data.
However, when concurrency takes place there is a gotcha. We have a choice: use manual synchronization or use ConcurrentSkipListSet from java.concurrent package.</p>

<h1 id="my-case">My case</h1>
<p>I have a task that gets episodes from a server in a bulk operation. For example, when there is an 800 episodes total it makes 8 asynchronous requests 100 episodes each.
Resulting list of episodes needs to be sorted and displayed on the client side.</p>

<p>Code for manual synchronizations looks like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>final Set&lt;Video&gt; allEpisodes = new TreeSet&lt;&gt;(sortByEpisode);

final Video[] sortedEpisodes;

synchronized (allEpisodes) {
	Collections.addAll(allEpisodes, notFakeVideos);
	sortedEpisodes = allEpisodes.toArray(Video.EMPTY_ARRAY);
}
//use of sortedEpisodes
</code></pre></div></div>
<p>which is somewhat verbose.</p>

<p>In the opposite, code with concurrent collection looks very clean.:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>final Set&lt;Video&gt; allEpisodes = new ConcurrentSkipListSet&lt;&gt;(sortByEpisode);

Collections.addAll(allEpisodes, notFakeVideos);

final Video[] sortedEpisodes = allEpisodes.toArray(Video.EMPTY_ARRAY);

</code></pre></div></div>
<p>Almost none of the concurrent stuff is visible to the programmer which is a good thing for maintainability and error-proneness.</p>

<h1 id="benchmark">Benchmark?</h1>

<p>I’m tested two solutions using Android emulator API 30. Here are results:</p>

<table>
  <thead>
    <tr>
      <th>solution</th>
      <th>avg ns</th>
      <th>min ns</th>
      <th>max ns</th>
      <th># of experiments</th>
      <th>sum ns</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>concurrent</td>
      <td>114960</td>
      <td>6543</td>
      <td>4109831</td>
      <td>620</td>
      <td>71275591</td>
    </tr>
    <tr>
      <td>synchronized</td>
      <td>44295</td>
      <td>7035</td>
      <td>1525028</td>
      <td>620</td>
      <td>27463261</td>
    </tr>
  </tbody>
</table>

<p>For my use case manual synchronization is almost twice as fast as using concurrent collection.</p>

<h1 id="but-which-is-better">But which is better?</h1>
<p>Winner: TreeSet+synchronized!
However, in broad case the answer is: “It depends”. 
My case consist of only 10 invocations of adding elements and one <code class="language-plaintext highlighter-rouge">toArray()</code> call. It is a tiny number of concurrent events. 
That’s why concurrent collection overhead makes a visible difference for performance.</p>]]></content><author><name>Dmitry Samoylenko</name></author><summary type="html"><![CDATA[What’s the difference Those two collections are all sorted and very useful when you need to maintain some order in dynamically filled data. However, when concurrency takes place there is a gotcha. We have a choice: use manual synchronization or use ConcurrentSkipListSet from java.concurrent package.]]></summary></entry><entry><title type="html">First Look at Jetpack Compose</title><link href="https://dmitrysamoylenko.com/2020/09/13/jetpack_compose.html" rel="alternate" type="text/html" title="First Look at Jetpack Compose" /><published>2020-09-13T00:00:00+00:00</published><updated>2020-09-13T00:00:00+00:00</updated><id>https://dmitrysamoylenko.com/2020/09/13/jetpack_compose</id><content type="html" xml:base="https://dmitrysamoylenko.com/2020/09/13/jetpack_compose.html"><![CDATA[<h1 id="lets-create-a-hundred-thousand-views">Let’s Create a Hundred Thousand Views</h1>
<p>I should say upfront that this article is not intended to nitpick this library, as I personally find it quite appealing. 
First, we’ll create a simple list to add views to. 
This is an inefficient method, but it will illustrate the overall picture of how much attention was paid to performance in the new framework.
So, let’s compare the old method:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val N = 100_000
        setContentView(
            ScrollView(this).apply {
                addView(
                    LinearLayout(context).apply {
                        orientation = LinearLayout.VERTICAL
                        repeat(N) {
                            addView(
                                TextView(context)
                                    .apply { text = "hello $it" })
                        }
                    })
            })
    }
}
</code></pre></div></div>
<p>And the new method:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val N = 100_000
        setContent {
            MyApplicationTheme {
                Surface(color = MaterialTheme.colors.background) {
                    ScrollableColumn {
                        repeat(N) {
                            Text(text = "Hello $it")
                        }
                    }
                }
            }
        }
    }
}
</code></pre></div></div>
<p>This is a simple scrolling list, where N text views are arranged vertically.</p>

<p>We measure performance using Android Studio’s <code class="language-plaintext highlighter-rouge">Profiler</code> tab after the heap growth stabilizes.</p>
<ol>
  <li>
    <p>N = 1
<img src="https://dmitrysamoylenko.com/assets/c_1.png" alt="c_1" />
Heap size for View - 1.5M vs Compose - 2.7M
This is the basic difference with just one view. Twice as much, but not critical for modern devices.</p>
  </li>
  <li>
    <p>N = 40,000
<img src="https://dmitrysamoylenko.com/assets/c_2.png" alt="c_2" />
View 723M vs Compose 26778M
You can see how memory usage significantly increases depending on the number of elements in compose.</p>
  </li>
  <li>
    <p>N = 60,000 here the emulator ran out of heap space of 512 MB (with allocated RAM=30GB) and compose crashed with OutOfMemoryError</p>
  </li>
  <li>
    <p>N = 100,000 continue testing View - 1807M. There’s a huge potential for growth in the number of elements present at the same time.
<img src="https://dmitrysamoylenko.com/assets/c_3.png" alt="c_3" /></p>
  </li>
</ol>

<p>Let’s plot the memory growth against the number of elements.
<img src="https://dmitrysamoylenko.com/assets/c_4.png" alt="c_4" />
Memory usage for View grows linearly, which is not the case for Compose.</p>

<h1 id="the-real-power-of-jetpack-compose">The Real Power of Jetpack Compose</h1>

<p>It takes just 3 lines to create a lazy list like RecyclerView</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LazyColumnFor(items = (1..1_000_000).toList()) {
  Text(text="Hello $it")
}
</code></pre></div></div>
<p>For comparison, achieving the same result using RecyclerView:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RecyclerView(this).apply {
  adapter = object : RecyclerView.Adapter&lt;RecyclerView.ViewHolder&gt;() {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        object : RecyclerView.ViewHolder(TextView(context)) {}

     override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder.itemView as TextView).text = "hello $position"
     }

     override fun getItemCount() = N

  }
  layoutManager = LinearLayoutManager(context)
}

</code></pre></div></div>
<p>Let’s take a closer look at the Lazy list. 
It’s interesting to see how often it redraws elements. 
Let’s draw multicolored circles using Canvas:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LazyColumnFor(items = (1..N).toList()) {
  Text(text = "Hello $it")
  Canvas(modifier = Modifier.size(10.dp), onDraw = {
     this.drawOval(
        color = Color(
           Math.random().toFloat(),
           Math.random().toFloat(),
           Math.random().toFloat()
        ), size = this.size.times(3.0f)
     )
  })
}

</code></pre></div></div>

<p>As we can see, Canvas() redraws every tick
<img src="https://dmitrysamoylenko.com/assets/compose_lazy_flashes.gif" alt="compose_lazy_flashes.gif" /></p>

<p>However, Text() redraws only when it leaves the visible area:
<img src="https://dmitrysamoylenko.com/assets/compose_lazy_text_redraw.gif" alt="compose_lazy_text_redraw.gif" /></p>

<p>This is encouraging: they are thinking about component redraw optimizations in advance.</p>

<p>Also, the <code class="language-plaintext highlighter-rouge">LazyColumnFor(items)</code> function interface does not yet allow creating a truly infinite list. A finite set of items is always expected.</p>

<h1 id="tldr">tl;dr</h1>
<p>Jetpack Compose is still in alpha version and it’s evident that priority is given to API conciseness. Let’s hope that Google doesn’t stop at the first iteration and optimizes the components.</p>

<p>After all, it’s their own slogan - #pert_matters, which they gradually seem to forget <a href="https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE">https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE</a>)</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/videoseries?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>]]></content><author><name>Dmitry Samoylenko</name></author><summary type="html"><![CDATA[Let’s Create a Hundred Thousand Views I should say upfront that this article is not intended to nitpick this library, as I personally find it quite appealing. First, we’ll create a simple list to add views to. This is an inefficient method, but it will illustrate the overall picture of how much attention was paid to performance in the new framework. So, let’s compare the old method: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val N = 100_000 setContentView( ScrollView(this).apply { addView( LinearLayout(context).apply { orientation = LinearLayout.VERTICAL repeat(N) { addView( TextView(context) .apply { text = "hello $it" }) } }) }) } } And the new method: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val N = 100_000 setContent { MyApplicationTheme { Surface(color = MaterialTheme.colors.background) { ScrollableColumn { repeat(N) { Text(text = "Hello $it") } } } } } } } This is a simple scrolling list, where N text views are arranged vertically.]]></summary></entry><entry><title type="html">Implementing a Global try-catch for an Android Application</title><link href="https://dmitrysamoylenko.com/2020/08/19/android_try_catch.html" rel="alternate" type="text/html" title="Implementing a Global try-catch for an Android Application" /><published>2020-08-19T00:00:00+00:00</published><updated>2020-08-19T00:00:00+00:00</updated><id>https://dmitrysamoylenko.com/2020/08/19/android_try_catch</id><content type="html" xml:base="https://dmitrysamoylenko.com/2020/08/19/android_try_catch.html"><![CDATA[<h1 id="where-in-the-application-is-the-main-method-hidden">Where in the Application is the main() Method Hidden?</h1>

<p>It can be found empirically. Set a breakpoint in the Application.onCreate callback and
you will discover it at the top of the stack trace, in the class ActivityThread.
Here it is, the “familiar” entry point of a JVM application:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        ...

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

</code></pre></div></div>
<p>Skipping things irrelevant to our topic, here’s what’s happening:</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">Looper.prepareMainLooper()</code> “prepares” the Looper (creates a thread-local instance of the Looper class)</li>
  <li><code class="language-plaintext highlighter-rouge">ActivityThread thread =...</code> An instance of ActivityThread is created</li>
  <li><code class="language-plaintext highlighter-rouge">thread.attach</code> The attach method of ActivityThread is executed (creates an instance of the application and calls the Application.onCreate callback)</li>
  <li><code class="language-plaintext highlighter-rouge">Looper.loop()</code> The looper starts. From this moment, the looper begins to poll its internal queue and execute
tasks from it. All other activity callbacks will end up here.</li>
  <li><code class="language-plaintext highlighter-rouge">throw new RuntimeException("Main thread loop unexpectedly exited")</code> The last line reveals
the essence of the SDK architecture - the application should not reach this line in its lifetime. If we do, 
the application terminates with an error.
Thus, a problem and its solution emerge: if some random Activity or View callback throws an error, the entire application crashes. There’s no way to safeguard against this in advance.
In Java, there is a universal global mechanism for catching errors:</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>		Thread.currentThread().setUncaughtExceptionHandler((t, e) -&gt; ... ); //ловим все ошибки
</code></pre></div></div>
<p>But the above architecture doesn’t allow this mechanism to be used, as the error will first be thrown in the Looper.loop() call, ending it, and only then it will come out in main() and our set error handler.
Once the error is caught by the handler, we are left with the problem of the completed Looper.loop, and therefore
an application that no longer responds to system callbacks and clicks.</p>

<h1 id="solution-to-the-frozen-application-problem">Solution to the Frozen Application Problem</h1>
<p>To get the activity to respond to clicks and callbacks again, just restart the looper:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>		Looper.loop();
</code></pre></div></div>

<p>So, the final solution. In Application.onCreate:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>		Thread.currentThread().setUncaughtExceptionHandler((t, e) -&gt; continueSafeLoop(e));
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>	public static void continueSafeLoop(final Throwable e) {
		Throwable error = e;
		while (true) {
			Assert.fail(error);
			try {
				final Looper looper = Looper.myLooper();
				new Handler(looper).removeCallbacksAndMessages(null);
				final MessageQueue queue = ReflectUtils.readField(looper, "mQueue");
				final Long ptr = ReflectUtils.readField(queue, "mPtr");
				final Boolean quitting = ReflectUtils.readField(queue, "mQuitting");
				if ((ptr == null || ptr != 0) &amp;&amp; (quitting == null || !quitting.booleanValue())) {
					Looper.loop();
				} else {
				break; //this is just detecting the end of the looper; done only reflectively
				}
			} catch (final Throwable err) {
				error = err;
			}
		}
	}
</code></pre></div></div>
<p>Now you can experiment by throwing an error in any of the callbacks and see that the application does not crash
and does not freeze.
Of course, you will still need to abstract away from direct activity callbacks, as there is a detect call to the super method.
And you must ensure sending caught crashes to Firebase/Crashlytics, as there will no longer be crashes
in the standard error reports in the Google Play console.</p>

<p>P.S.: It is noteworthy that the class ActivityThread is not a Thread and is not about the Android class
Activity, but rather about “activity” in the sense of “a set of actions,” and creates not one activity, but the entire Application.
In essence, it acts as a delegate of all system callbacks of the application, responsible for its state transitions.
This is also stated in its JavaDoc:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/**
 * This manages the execution of the main thread in an
 * application process, scheduling and executing activities,
 * broadcasts, and other operations on it as the activity
 * manager requests.
 *
 * {@hide}
 */
</code></pre></div></div>
<p>A bit of an unfortunate name choice, in my opinion :)</p>]]></content><author><name>Dmitry Samoylenko</name></author><summary type="html"><![CDATA[Where in the Application is the main() Method Hidden?]]></summary></entry><entry><title type="html">A Cool Refactoring Hidden in IntelliJ IDEA</title><link href="https://dmitrysamoylenko.com/2020/07/06/cool_intellij_idea.html" rel="alternate" type="text/html" title="A Cool Refactoring Hidden in IntelliJ IDEA" /><published>2020-07-06T00:00:00+00:00</published><updated>2020-07-06T00:00:00+00:00</updated><id>https://dmitrysamoylenko.com/2020/07/06/cool_intellij_idea</id><content type="html" xml:base="https://dmitrysamoylenko.com/2020/07/06/cool_intellij_idea.html"><![CDATA[<h1 id="this-is-about-replacing-field-access-with-a-getter">This is about replacing field access with a getter</h1>
<p>By default, there’s a visible refactoring option for this through <code class="language-plaintext highlighter-rouge">alt+insert</code> - <code class="language-plaintext highlighter-rouge">getter</code>.</p>

<p><img src="https://dmitrysamoylenko.com/assets/enq1.png" alt="enq1" /></p>

<p>The problem is that if a field is already used in many places, just creating a getter is not enough. You also need to replace direct access with this getter everywhere.</p>

<p><img src="https://dmitrysamoylenko.com/assets/enq2.png" alt="enq2" /></p>

<p>Fortunately, JetBrains has thought of this! 
Press <code class="language-plaintext highlighter-rouge">ctrl+shift+A</code>, enter and open the <code class="language-plaintext highlighter-rouge">refactor this</code> action.</p>

<p><img src="https://dmitrysamoylenko.com/assets/enq3.png" alt="enq3" /></p>

<p>In the menu, there are many different refactorings. We select <code class="language-plaintext highlighter-rouge">encapsulate</code>.</p>

<p><img src="https://dmitrysamoylenko.com/assets/enq4.png" alt="enq4" /></p>

<p>A dialog will open where you select the needed fields, tick the getters/setters, and click <code class="language-plaintext highlighter-rouge">refactor</code>.</p>

<p><img src="https://dmitrysamoylenko.com/assets/enq5.png" alt="enq5" /></p>

<p>Done!</p>

<p><img src="https://dmitrysamoylenko.com/assets/enq6.png" alt="enq6" /></p>

<p>As we can see, direct access has been replaced with the getter throughout the code.</p>

<p><img src="https://dmitrysamoylenko.com/assets/enq7.png" alt="enq7" /></p>]]></content><author><name>Dmitry Samoylenko</name></author><summary type="html"><![CDATA[This is about replacing field access with a getter By default, there’s a visible refactoring option for this through alt+insert - getter.]]></summary></entry></feed>