<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://angrynerds.co/blog/rss/xslt"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>Blog</title>
    <link>https://angrynerds.co/blog</link>
    <description />
    <generator>Articulate, blogging built on Umbraco</generator>
    <item>
      <guid isPermaLink="false">4111</guid>
      <link>https://angrynerds.co/blog/kotlin-sharedflow-or-how-i-learned-to-stop-using-rxjava/</link>
      <category>Development</category>
      <title>Kotlin SharedFlow or: How I learned to stop using RxJava and love the Flow</title>
      <description>&lt;p&gt;Coroutines are great. Period. When they were introduced along with Kotlin 1.3 we immediately knew that they were going to be a game changer. And they are indeed. Easy to learn. Easy to understand. And so easy to use. When &lt;code&gt;coroutines 1.0.0&lt;/code&gt; were released, many anticipated that it would be the end for RxJava on Android. &lt;/p&gt;
&lt;p&gt;It turned out that despite coroutines being great, RxJava was still being picked for many new projects. Why? Probably one of the reasons was that it was well known, sure. But there was also a more important reason: coroutines still couldn’t be used in some use cases where RxJava could.&lt;/p&gt;
&lt;h2&gt;RxJava is not only &lt;code&gt;Single&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;In the majority of the apps RxJava was used mostly for API requests handling. We all know that – make a REST API call with Retrofit, return RxJava Single, apply proper schedulers, and then map the response. In this scenario, coroutines are a great fit. We just need to change our endpoint methods to suspending ones, and we’re more or less ready to go. But of course, RxJava is much more than that, right? The real strength of reactive programming is processing of ongoing data streams.&lt;/p&gt;
&lt;p&gt;Example 1: we have a few-step data upload process in the app (done in a background thread), and the update at each step should cause real-time update of the UI. Seems like a great case for good old &lt;code&gt;Observable&lt;/code&gt; and its &lt;code&gt;onNext()&lt;/code&gt;. This scenario could not be handled with plain coroutines, as &lt;code&gt;suspend&lt;/code&gt; functions can return only a single value. So we lacked a proper tool. It all changed with the introduction of &lt;code&gt;coroutines 1.2.0&lt;/code&gt;, when we were given Kotlin Flow. A mighty Kotlin Flow that handled (almost) all cases that previously could be handled only with RxJava, and what’s the most important – it was handling them using coroutines.&lt;/p&gt;
&lt;p&gt;If you have any experience with both RxJava and coroutines, switching to Flows should be really seamless, as their syntax and logic is using both aforementioned concepts. Let’s see the example code for Example1.&lt;/p&gt;
&lt;p&gt;Using RxJava:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fun uploadData(): Observable&amp;lt;UpdateStep&amp;gt; = Observable.create&amp;lt;UpdateStep&amp;gt; {
    it.onNext(UpdateStep.Step1)
    doUpdateStep1()
    it.onNext(UpdateStep.Step2)
    doUpdateStep2()
    it.onNext(UpdateStep.Step3)
    doUpdateStep3()
    it.onNext(UpdateStep.Success)
}.onErrorReturn {
    UpdateStep.Error(it)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;
And using Flow:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fun uploadData(): Flow&amp;lt;UpdateStep&amp;gt; = flow {
    emit(UpdateStep.Step1)
    doUpdateStep1()
    emit(UpdateStep.Step2)
    doUpdateStep2()
    emit(UpdateStep.Step3)
    doUpdateStep3()
    emit(UpdateStep.Success)
}.catch {
    emit(UpdateStep.Error(it))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;
Now we just need to &lt;code&gt;subscribe()&lt;/code&gt; our &lt;code&gt;Observable&lt;/code&gt; or &lt;code&gt;collect()&lt;/code&gt; our &lt;code&gt;Flow&lt;/code&gt;, and we’ve got all we need. Easy peasy. This way coroutines made another step to replace RxJava. However, that wasn’t the last step.&lt;/p&gt;
&lt;h2&gt;You’re hot then you’re cold&lt;/h2&gt;
&lt;p&gt;For anyone working with RxJava a bit more extensively, the concept of cold/hot streams is well known. Streams created by &lt;code&gt;Observable.create()&lt;/code&gt; or &lt;code&gt;Flowable.just()&lt;/code&gt; are cold, which means that they start executing the code and emitting values only when something subscribes to them. This is the case that can be also handled using &lt;code&gt;flow{}&lt;/code&gt; or &lt;code&gt;flowOf()&lt;/code&gt; - flows created with these methods are called cold flows (no surprise here). &lt;/p&gt;
&lt;p&gt;The last big RxJava use case that coroutines were still missing was handling hot flows. In reactive programming hot streams are the ones that don’t have to be observed by anything to emit values. This makes them a perfect candidate eg. for handling BLE connection state - app knows about connection state changes at all times, and informs observers if there are any, or as soon as they arrive (Example 2). Hot streams in RxJava can be created using eg. &lt;code&gt;PublishSubject&lt;/code&gt; and &lt;code&gt;BehaviorSubject&lt;/code&gt;. Here is how we can use them to implement Example2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class BleManager : BleConnectionObserver {
    private val _bleConnectionStateSubject = BehaviorSubject.createDefault(ConnectionState.Disconnected)
    val bleConnectionStateObservable: Observable&amp;lt;ConnectionState&amp;gt; = _bleConnectionStateSubject.hide()
    val currentConnectionState: ConnectionState
        get() = _bleConnectionStateSubject.value

    override fun onDeviceConnected(device: BluetoothDevice) {
        /* ... */
        _bleConnectionStateSubject.onNext(ConnectionState.Connected)
    }

    override fun onDeviceDisconnected() {
        /* ... */
        _bleConnectionStateSubject.onNext(ConnectionState.Disconnected)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;
For a long time, this part of functionality was missing in stable coroutines API, and that changed with the release of &lt;code&gt;coroutines 1.4.0&lt;/code&gt; in October 2020. But let’s take a step back. Even before Kotlin Flow was released, we heard of a concept called &lt;code&gt;channels&lt;/code&gt;. It was designed to solve the Producer-Consumer problem - of course using coroutines. &lt;code&gt;Channel&lt;/code&gt; interface, which is offering suspendable &lt;code&gt;send()&lt;/code&gt; and &lt;code&gt;receive()&lt;/code&gt; methods, seemed to be quite straightforward but in reality is a bit more complex. &lt;/p&gt;
&lt;p&gt;It is using independent coroutines on both sides of the communication that introduces concurrency which has to be handled by us, and may lead to some issues with concurrent access to the same resources. It also wasn’t helpful that channels API was still not fully stable – there were (and still are) many experimental/preview methods there. Lucky for us, the Kotlin team came up with a concept of &lt;a href="https://developer.android.com/kotlin/flow/stateflow-and-sharedflow" target="_blank"&gt;SharedFlow and StateFlow&lt;/a&gt; that doesn’t mess with multiple coroutines and makes our data flow much cleaner. If you want to read more about channels-flow relation, take a look at &lt;a href="https://elizarov.medium.com/shared-flows-broadcast-channels-899b675e805c" target="_blank"&gt;this great article by Roman Elizarov&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/2481/chris-j-davis-pt_9ux0j-x4-unsplash.jpg" alt="kotlin sharedflow" /&gt;&lt;/p&gt;
&lt;p&gt;Now to the best part. &lt;code&gt;SharedFlow&lt;/code&gt; is an equivalent of RxJava’s &lt;code&gt;PublishSubject&lt;/code&gt;. It allows us to create hot flows and specify strategies for handling backpressure and replay. &lt;code&gt;StateFlow&lt;/code&gt; is a special case of &lt;code&gt;SharedFlow&lt;/code&gt; which is an equivalent of RxJava’s &lt;code&gt;BehaviorSubject&lt;/code&gt;. It creates a hot flow that persists its latest value – which makes it a perfect candidate for handling BLE connection state from Example2.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class BleManager : BleConnectionObserver {
    private val _bleConnectionStateFlow = MutableStateFlow(ConnectionState.Disconnected)
    val bleConnectionStateFlow: StateFlow&amp;lt;ConnectionState&amp;gt; = _bleConnectionStateFlow

    override fun onDeviceConnected(device: BluetoothDevice) {
        /* ... */
        _bleConnectionStateFlow.value = ConnectionState.Connected
    }

    override fun onDeviceDisconnected() {
        /* ... */
        _bleConnectionStateFlow.value = ConnectionState.Disconnected
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;
It’s worth noticing that both Shared and State flows can be used in mutable form and be exposed as immutables – this concept is well known for anyone using &lt;code&gt;LiveData&lt;/code&gt;. In RxJava we had to hide BehaviorSubject behind plain &lt;code&gt;Observable&lt;/code&gt; and expose current state separately. With flows, the immutable form of &lt;code&gt;StateFlow&lt;/code&gt; still allows us to get its latest value.&lt;/p&gt;
&lt;p&gt;To use &lt;code&gt;SharedFlow&lt;/code&gt; and &lt;code&gt;StateFlow&lt;/code&gt; correctly we need to remember a few things:&lt;br /&gt;&lt;br /&gt;
▸ &lt;code&gt;StateFlow&lt;/code&gt; is conflated, which means that if we update its value with a new value that is equal to the previous one, the update will not be propagated.&lt;br /&gt;&lt;br /&gt;
▸ &lt;code&gt;SharedFlow&lt;/code&gt; needs to have a proper replay/buffer configuration. In most cases, we just need to make sure that our producer will not get suspended if there’s no consumer, eg. by setting replay or buffer capacity to a positive value or setting &lt;code&gt;bufferOverflow&lt;/code&gt; to something else than SUSPEND.&lt;br /&gt;&lt;br /&gt;
▸ &lt;code&gt;StateFlow&lt;/code&gt; and &lt;code&gt;SharedFlow&lt;/code&gt; will never complete, so you cannot depend on their &lt;code&gt;onCompletionCallback&lt;/code&gt;, and need to remember that collecting them will never finish normally (neither will the coroutine in which we called their &lt;code&gt;collect&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;Ok, cool, but do we even need that?&lt;/h2&gt;
&lt;p&gt;For me, &lt;code&gt;SharedFlow&lt;/code&gt; and &lt;code&gt;StateFlow&lt;/code&gt; were real game changers, and only when they were introduced I decided that I can fully quit using RxJava. What is more, recently &lt;code&gt;StateFlow&lt;/code&gt; also received &lt;a href="https://developer.android.com/topic/libraries/data-binding/observability#stateflow" target="_blank"&gt;alpha support for working with data binding&lt;/a&gt;. And what is great is that using &lt;code&gt;StateFlow&lt;/code&gt; in data binding is lifecycle aware – just like &lt;code&gt;LiveData&lt;/code&gt;! No more updating UI when it’s not ready ❤️  However, you need to remember that collecting &lt;code&gt;SharedFlow&lt;/code&gt; in the Fragment using &lt;code&gt;lifecycleScope.launch{}&lt;/code&gt; is not lifecycle aware – you need to use &lt;code&gt;launchWhenStarted&lt;/code&gt; or cancel the job when the app goes to background. This topic has been well discussed in &lt;a href="https://proandroiddev.com/should-we-choose-kotlins-stateflow-or-sharedflow-to-substitute-for-android-s-livedata-2d69f2bd6fa5" target="_blank"&gt;this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/2482/daniel-romero-le7klk9qcgo-unsplash.jpg" alt="kotlin coroutines" /&gt;&lt;/p&gt;
&lt;p&gt;Some of you may say: &lt;em&gt;“Why do we even need SharedFlow? Most of the described use cases can be handled with LiveData!”&lt;/em&gt;. And then I’ll ask: &lt;em&gt;“Have you ever heard of clean architecture?&amp;quot;&lt;/em&gt; 😉 Sure, when you need to observe changes from ViewModel in the Fragment, &lt;code&gt;LiveData&lt;/code&gt; is absolutely enough, but sometimes you need to observe events in different modules, or in general – somewhere behind the abstraction layer. And &lt;code&gt;LiveData&lt;/code&gt; unfortunately won’t work in this case because it is an Android class, and our domain module (or in general - base abstraction layer) should be pure Kotlin. &lt;/p&gt;
&lt;p&gt;This is where &lt;code&gt;SharedFlow&lt;/code&gt; purely shines, because it is a Kotlin class. We can expose it from our database or API without tight coupling our domain code to Android classes. Sounds like a really nice approach 😀  What is more, it seems that &lt;code&gt;SharedFlow&lt;/code&gt; can be used as an alternative solution for the &lt;a href="https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150" target="_blank"&gt;SingleLiveEvent problem&lt;/a&gt; that needs a special approach when using &lt;code&gt;LiveData&lt;/code&gt;.&lt;/p&gt;
&lt;h5&gt;I’ve been using SharedFlow and StateFlow in my latest project for about 7 months now, and they’re really fun to use!&lt;/h5&gt;
&lt;h5&gt;Have you already tried SharedFlow or StateFlow? What are your thoughts about them? I know that I finally can stop using RxJava and just love the Flow 😀&lt;/h5&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;About the Author&lt;/strong&gt;&lt;br /&gt; 
&lt;a href="https://www.linkedin.com/in/aleksandra-a-krzemien/" target="_blank"&gt;Aleksandra Krzemień&lt;/a&gt; has profound experience in building all sorts of mobile apps, including those connected with external devices. She is a natural mentor and loves to share her knowledge. Throughout her career, she has been actively engaged in many mentoring programs and IT-related organizations, such as Women in Technology or Google Developers Group. She likes to spend her free time singing and playing board games.&lt;/p&gt;
</description>
      <pubDate>Thu, 29 Apr 2021 07:39:31 Z</pubDate>
      <a10:updated>2021-04-29T07:39:31Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">3221</guid>
      <link>https://angrynerds.co/blog/motionlayout-plus-android-studio-40/</link>
      <category>Development</category>
      <title>MotionLayout + Android Studio 4.0 = ❤</title>
      <description>&lt;p&gt;MotionLayout is out there since the release of &lt;code&gt;constraint-layout:2.0.0-alpha1&lt;/code&gt; (June 2018), and it has already caused quite a stir. When we first saw the example animations that can be done pretty easily using it, we were pretty impressed. However, as it turned out, using MotionLayout was time- and work-consuming, because there was a lot of code to write, and in order to test it we had to run our app on the device each time.&lt;/p&gt;
&lt;p&gt;These times are over!&lt;/p&gt;
&lt;p&gt;As for &lt;code&gt;constraint-layout:2.0.0-beta3&lt;/code&gt; and &lt;strong&gt;&lt;em&gt;Android Studio 4.0 Canary1&lt;/em&gt;&lt;/strong&gt; we can edit MotionLayout in the graphic design tool, and what’s even better, we can preview the effects of our work right in the Android Studio, with no need to run the app on the device ❤&lt;/p&gt;
&lt;p&gt;That sounds really awesome. Now, let’s see how it works.&lt;/p&gt;
&lt;div style="display: block; width: 300px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1731/keyframe2.gif"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align="center"&gt;

&lt;i&gt;A simple MotionLayout with changing position, color, rotation, and size of the view.&lt;/i&gt;

&lt;/div&gt;
&lt;h2&gt;MotionLayout&lt;/h2&gt;
&lt;p&gt;At the beginning, just a few words about the MotionLayout itself. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;MotionLayout&lt;/code&gt; class extends &lt;code&gt;ConstraintLayout&lt;/code&gt;. That means you can seamlessly convert the latter to the first, and without any changes, it should work pretty much the same. That is really nice, because it enables us to experiment with MotionLayout, starting with any existing ConstraintLayout.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;androidx.constraintlayout.widget.ConstraintLayout 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;
    android:layout_width=&amp;quot;match_parent&amp;quot;
    android:layout_height=&amp;quot;match_parent&amp;quot;&amp;gt;

    &amp;lt;View
        android:id=&amp;quot;@+id/sky&amp;quot;
        android:layout_width=&amp;quot;0dp&amp;quot;
        android:layout_height=&amp;quot;0dp&amp;quot;
        android:background=&amp;quot;@color/sky_morning&amp;quot;
        app:layout_constraintBottom_toTopOf=&amp;quot;@id/guideline&amp;quot;
        app:layout_constraintEnd_toEndOf=&amp;quot;parent&amp;quot;
        app:layout_constraintStart_toStartOf=&amp;quot;parent&amp;quot;
        app:layout_constraintTop_toTopOf=&amp;quot;parent&amp;quot; /&amp;gt;

    &amp;lt;View
        android:id=&amp;quot;@+id/ground&amp;quot;
        android:layout_width=&amp;quot;0dp&amp;quot;
        android:layout_height=&amp;quot;0dp&amp;quot;
        android:background=&amp;quot;@color/ground&amp;quot;
        app:layout_constraintBottom_toBottomOf=&amp;quot;parent&amp;quot;
        app:layout_constraintEnd_toEndOf=&amp;quot;parent&amp;quot;
        app:layout_constraintStart_toStartOf=&amp;quot;parent&amp;quot;
        app:layout_constraintTop_toBottomOf=&amp;quot;@id/guideline&amp;quot; /&amp;gt;

    &amp;lt;androidx.constraintlayout.widget.Guideline
        android:id=&amp;quot;@+id/guideline&amp;quot;
        android:layout_width=&amp;quot;wrap_content&amp;quot;
        android:layout_height=&amp;quot;wrap_content&amp;quot;
        android:orientation=&amp;quot;horizontal&amp;quot;
        app:layout_constraintGuide_percent=&amp;quot;0.5&amp;quot; /&amp;gt;

    &amp;lt;ImageView
        android:id=&amp;quot;@+id/biker&amp;quot;
        android:layout_width=&amp;quot;wrap_content&amp;quot;
        android:layout_height=&amp;quot;wrap_content&amp;quot;
        android:contentDescription=&amp;quot;@string/biker&amp;quot;
        android:src=&amp;quot;@drawable/ic_bike&amp;quot;
        app:layout_constraintBottom_toBottomOf=&amp;quot;@id/guideline&amp;quot;
        app:layout_constraintTop_toTopOf=&amp;quot;@id/guideline&amp;quot; /&amp;gt;

&amp;lt;/androidx.constraintlayout.widget.ConstraintLayout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;div align="center"&gt;

&lt;i&gt;Above: just a simple constraint layout that will be our starting point.&lt;/i&gt;

&lt;/div&gt;
&lt;p&gt;&lt;br&gt;
The real magic of MotionLayout lays in the MotionScene resource file, which contains all the constraints and attributes transitions that result in our layout animation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;MotionScene 
    xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;
    xmlns:motion=&amp;quot;http://schemas.android.com/apk/res-auto&amp;quot;&amp;gt;

    &amp;lt;Transition
        motion:constraintSetEnd=&amp;quot;@+id/end&amp;quot;
        motion:constraintSetStart=&amp;quot;@id/start&amp;quot;
        motion:duration=&amp;quot;1000&amp;quot;&amp;gt;
       &amp;lt;KeyFrameSet&amp;gt;
       &amp;lt;/KeyFrameSet&amp;gt;
    &amp;lt;/Transition&amp;gt;

    &amp;lt;ConstraintSet android:id=&amp;quot;@+id/start&amp;quot;&amp;gt;
    &amp;lt;/ConstraintSet&amp;gt;

    &amp;lt;ConstraintSet android:id=&amp;quot;@+id/end&amp;quot;&amp;gt;
    &amp;lt;/ConstraintSet&amp;gt;
&amp;lt;/MotionScene&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;div align="center"&gt;

&lt;i&gt;Above: an empty MotionScene file.&lt;/i&gt;

&lt;/div&gt;
&lt;p&gt;&lt;br&gt;
Each Transition has to have a start and end ConstraintSet, and a number of KeyFrames that enables us to achieve various interesting effects. You should remember, that &lt;u&gt;constraints set in the MotionScene always take precedence over these declared in the MotionLayout file.&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;If you want to know more about the MotionLayout itself, there is a great series of articles on the &lt;a href="https://medium.com/google-developers/introduction-to-motionlayout-part-i-29208674b10d" target="_blank"&gt;Google Developers Medium&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To use all the discussed below features you need to add a dependency to your &lt;code&gt;build.gradle&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dependencies {
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;MotionEditor&lt;/h2&gt;
&lt;p&gt;Ok, so now that we know what MotionLayout is, let’s see what Android Studio 4.0 can do to make our work with it more enjoyable.&lt;/p&gt;
&lt;p&gt;First of all, in the layout design panel there is a tool to automatically convert our selected ConstraintLayout to the MotionLayout. &lt;/p&gt;
&lt;div style="display: block; width: 600px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1748/convert.png"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;What it really does is changing the root layout to MotionLayout, creating a new MotionScene xml file in the &lt;code&gt;resources/xml&lt;/code&gt; package and setting the &lt;code&gt;app:layoutDescription&lt;/code&gt; attribute of the new layout to the motion scene file. That’s it. But is also redirects us to the brand new MotionEditor view.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/1739/start_motion.png" /&gt;&lt;/p&gt;
&lt;p&gt;There you can see a preview of our MotionLayout on the left, a panel with all the transitions and constraint sets in the middle, and attributes panel on the right.&lt;/p&gt;
&lt;p&gt;One thing to remember is that you can navigate between code, design, and split the screen using the three buttons on the top right corner of the view (not on the bottom left as it was before).&lt;/p&gt;
&lt;p&gt;Now, by navigating between the start and end states in the middle panel, we are able to add/change views constraints and attributes. E.g. we want the color of the sky to change from light to dark. All we need to do is to choose start state, click on &lt;code&gt;sky&lt;/code&gt; entry and add a new Custom Attribute (there is an autocomplete in the attributes popups ☺). Then repeat it for the end state. &lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/1740/end_motion1.png" /&gt;&lt;/p&gt;
&lt;p&gt;MotionLayout will do the rest for us, and we get a nice simple sky animation.&lt;/p&gt;
&lt;p&gt;In a similar way, but by changing the start and end constraints of the &lt;code&gt;biker&lt;/code&gt; image, we are able to create a changing position animation. &lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/1738/end_motion2.png" /&gt;&lt;/p&gt;
&lt;p&gt;By clicking on the grey arrow between the states, you open the Transition panel where you can play the current motion animation.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/1737/transition_1.png" /&gt;&lt;/p&gt;
&lt;p&gt;To make it work on the device we need to start the motion somehow. We can either start/set the progress in code, or add an OnSwipe/OnClick MotionObject. In this example, we choose the second option, and by clicking the third button on top of the motion panel we add a SwipeHandler with an anchor at the biker.&lt;/p&gt;
&lt;div style="display: block; width: 250px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1735/motion_creation_icons.png"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div style="display: block; width: 300px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1736/step1_1.gif"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Now let’s add some sunshine to our animation ☺&lt;/p&gt;
&lt;p&gt;We add the sun image, and want it to move above the biker, but on the arc, so simply changing the constrains won’t be sufficient. We need to add also a KeyFrame. We can do it by choosing &lt;code&gt;KeyPosition&lt;/code&gt; from the Transition panel, and setting the &lt;code&gt;percentY&lt;/code&gt; position in a 50th frame (frames go from 0 to 100) to a negative value, and type do &lt;code&gt;pathRelative&lt;/code&gt;.&lt;/p&gt;
&lt;div style="display: block; width: 500px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1746/transition_0_1.png"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div style="display: block; width: 500px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1747/transition_2_1.png"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/1732/transition_3.png" /&gt;&lt;/p&gt;
&lt;p&gt;Now our animation starts to gain some life.&lt;/p&gt;
&lt;div style="display: block; width: 300px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1744/step2_1.gif"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;In a similar way, but using &lt;code&gt;KeyAttribute&lt;/code&gt; we can modify things like scale, rotation or alpha of our view.&lt;/p&gt;
&lt;div style="display: block; width: 400px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1749/key_attribute.png"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Let’s get nuts and add all of the above attributes! Making the sun do a 360 degrees rotation during the animation, scaling to 1.5 in the middle, and vanishing after the 60th frame.&lt;/p&gt;
&lt;div style="display: block; width: 600px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1734/sun_attributes.png"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;When we add a moon image with the same path as the sun image, and with alpha increasing after 60th, we’ll get a really nice transition.&lt;/p&gt;
&lt;div style="display: block; width: 300px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1733/step3_1.gif"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div align="center"&gt;

&lt;i&gt;Here, I added also a backgroundColor frame for sky at the 60th frame.&lt;/i&gt;

&lt;/div&gt;
&lt;p&gt;&lt;br&gt;
Now you can use your imagination and add other views to the animation. Will you be able to recreate the one from below? ☺&lt;/p&gt;
&lt;div style="display: block; width: 300px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1741/step4_1.gif"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;This kind of animation is a great example of how MotionLayout can be mixed with a &lt;code&gt;ViewPager&lt;/code&gt; for creating a really nice custom experience. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You can find the complete code of the example &lt;a href="https://github.com/aleksandra-majchrzak/motion-layout-examples" target="_blank"&gt;HERE&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div style="display: block; width: 300px; margin: 0 auto;"&gt;
&lt;img src="https://angrynerds.co/media/1743/pager.gif"&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;It is also worth to mention that if you have a MotionLayout written with an older constraint-layout version and older Android Studio, you can still use it with the Android Studio 4.0, and MotionEditor will work just fine, no changes need to be done ✔&lt;/p&gt;
&lt;h2&gt;Problems&lt;/h2&gt;
&lt;p&gt;The tools that Android Studio 4.0 gives us to work with the MotionLayout are great, however we must remember that neither of them is stable yet, and definitely not production-ready. There still can occur some random crashes and other errors.&lt;/p&gt;
&lt;p&gt;Here are some problems I encountered while playing with the above example:&lt;br&gt;
• modifying constraints can sometimes cause big lags&lt;br&gt;
• there must be no errors or &lt;u&gt;warnings (!)&lt;/u&gt; in your xml code for MotionEditor to work&lt;br&gt;
• ‘showPaths’ option does not work when there are many views in the layout&lt;br&gt;
• MotionEditor does not work for your custom classes extending MotionLayout&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Now it’s time for your own experiments. MotionLayout gives us great abilities to create beautiful and meaningful animations. There are many other features that I haven’t discussed here, like multiple transitions in a single MotionScene, creating custom attributes, using MotionLayout progress, KeyCycles, or motion easing.&lt;/p&gt;
&lt;p&gt;The best place to start your adventure is definitely the &lt;a href="https://developer.android.com/training/constraint-layout/motionlayout/examples" target="_blank"&gt;documentation page&lt;/a&gt;, where you can find many examples.&lt;/p&gt;
&lt;h3&gt;If you have any questions regarding the article, or want to share your MotionLayout animations, feel free to leave a comment!&lt;/h3&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;About the Author&lt;/strong&gt;&lt;br /&gt; 
&lt;a href="https://www.linkedin.com/in/aleksandra-a-krzemien/" target="_blank"&gt;Aleksandra Krzemień&lt;/a&gt; has profound experience in building all sorts of mobile apps, including those connected with external devices. She is a natural mentor and loves to share her knowledge. Throughout her career, she has been actively engaged in many mentoring programs and IT-related organizations, such as Women in Technology or Google Developers Group. She likes to spend her free time singing and playing board games.&lt;/p&gt;
</description>
      <pubDate>Fri, 15 Nov 2019 14:21:28 Z</pubDate>
      <a10:updated>2019-11-15T14:21:28Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">3211</guid>
      <link>https://angrynerds.co/blog/couchbase-lite-android-migration-from-14-to-2x/</link>
      <category>Development</category>
      <title>Couchbase Lite Android migration from 1.4 to 2.x</title>
      <description>&lt;p&gt;Couchbase Lite is out there for a while, but recently, with an update to version 2.0, it went through a big redesign. In this article, we’d like to point out some differences between versions 1.4 and 2.0, and help you with migrating your code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;[Note: this article does not cover migrating your data replication code.]&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All examples you’ll see in this article are based on the official sample &lt;a href="https://github.com/couchbaselabs/mobile-training-todo/tree/feature/2.0" target="_blank"&gt;Android Couchbase Lite Todo app&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Internal database format&lt;/h2&gt;
&lt;p&gt;Couchbase Lite is a document database that stores data in JSON documents, however internally it uses an SQLite database. We will look into this database a few times in this article. The first two tables we’re interested in are &lt;strong&gt;&lt;em&gt;docs&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;revs&lt;/em&gt;&lt;/strong&gt;. The first one contains a list of all documents internal ids, and the ids you gave them:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/1565/docs.png" /&gt;&lt;/p&gt;
&lt;p&gt;The second table contains info about all documents current revisions:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/1564/revs.png" /&gt;&lt;/p&gt;
&lt;p&gt;Up to version 1.4 Couchbase Lite was using a map-reduce approach, which bases on creating &lt;strong&gt;views&lt;/strong&gt; (which work more or less like SQL indexes). It requires you to create a &lt;strong&gt;map&lt;/strong&gt; (and optional &lt;strong&gt;reduce&lt;/strong&gt;) function, which given a JSON document input emits a number of key-value pairs to be indexed in the view. In code it would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val view = db.getView(&amp;quot;list/listByName&amp;quot;)
view.setMap({ document, emitter -&amp;gt;
               if (document[&amp;quot;type&amp;quot;] == &amp;quot;task-list&amp;quot;) {
                   emitter.emit(document[&amp;quot;name&amp;quot;], null)
               }
           }, &amp;quot;1&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;
Executing this code will result in inserting a new row in the internal database in &lt;strong&gt;&lt;em&gt;views&lt;/em&gt;&lt;/strong&gt; table:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/1561/views1.png" /&gt;&lt;/p&gt;
&lt;p&gt;and creating a new table &lt;strong&gt;&lt;em&gt;maps_1&lt;/em&gt;&lt;/strong&gt; which will include all emitted values. In this case:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/1567/maps1.png" /&gt;&lt;/p&gt;
&lt;p&gt;A new table &lt;strong&gt;&lt;em&gt;maps_x&lt;/em&gt;&lt;/strong&gt; will be created for every view.&lt;/p&gt;
&lt;p&gt;The first thing that changed in 2.0 version is the format of the internal SQLite database. Now the table we’re interested in is named &lt;strong&gt;&lt;em&gt;kv_default&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://angrynerds.co/media/1566/updated.png" /&gt;&lt;/p&gt;
&lt;p&gt;It contains all the info about the documents, but JSON body is stored in a binary form, so we’re no longer able to read it! The only way to read the data is to use an official &lt;a href="https://github.com/couchbase/couchbase-lite-core/wiki/The-'cblite'-Tool" target="_blank"&gt;cblite tool&lt;/a&gt;. More info about the new db schema can be found &lt;a href="https://github.com/couchbase/couchbase-lite-core/wiki/Database-Schema" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The important thing is that if in your app you have a database in version 1.4, when you update the library version in your code, your database will be automatically updated to the new format on the first open.&lt;/p&gt;
&lt;h2&gt;Database creation&lt;/h2&gt;
&lt;p&gt;After changing your gradle dependency from &lt;code&gt;implementation &amp;quot;com.couchbase.lite:couchbase-lite-android:1.4.*&amp;quot;&lt;/code&gt;
to &lt;code&gt;implementation &amp;quot;com.couchbase.lite:couchbase-lite-android:2.0.0&amp;quot;&lt;/code&gt; and compiling the project, your code will light up with dozens of error messages. Let’s try to fix some of them step by step.&lt;/p&gt;
&lt;p&gt;First things first. In 1.4 to create and open your database you probably used code like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val manager = Manager(AndroidContext(context.applicationContext), Manager.DEFAULT_OPTIONS)
database = manager.getExistingDatabase(&amp;quot;dbName&amp;quot;)
if (database == null) {
   ZipUtils.unzip(context.assets.open(&amp;quot;dbName.zip&amp;quot;), context.filesDir)
   database = manager.getDatabase(&amp;quot;dbName&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;If you needed to have a preloaded database, you stored it in a zip file in assets and then unzipped it using ZipUtils, which was a utility class from the couchbase-lite library. Now you need to change it to&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val config = DatabaseConfiguration(context.applicationContext)
if (!Database.exists(&amp;quot;dbName&amp;quot;, context.filesDir)) {
   ZipUtils.unzip(context.assets.open(&amp;quot;dbName.zip&amp;quot;), context.filesDir)
}
database = Database(&amp;quot;dbName&amp;quot;, config)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;
and remember that &lt;strong&gt;&lt;em&gt;ZipUtils&lt;/em&gt;&lt;/strong&gt; class is no longer included in the library, so you need to write your own (or copy the old implementation from &lt;a href="https://docs.couchbase.com/couchbase-lite/2.1/java.html#loading-a-pre-built-database" target="_blank"&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;CRUD operations&lt;/h2&gt;
&lt;p&gt;As in the 2.0 version Couchbase Lite changed data manipulation approach from map-reduce to N1QL-based Query API, the CRUD operations changed a lot. Say goodbye to &lt;strong&gt;&lt;em&gt;getView()&lt;/em&gt;&lt;/strong&gt; methods, and say hello to &lt;strong&gt;&lt;em&gt;QueryBuilder&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;u&gt;Creating a single&lt;/u&gt; document didn’t change that much. From&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val docId = username + &amp;quot;.&amp;quot; + UUID.randomUUID()
val data = mapOf(&amp;quot;type&amp;quot; to &amp;quot;task-list&amp;quot;, &amp;quot;name&amp;quot; to title, &amp;quot;owner&amp;quot; to username)
val document = Document(db, docId)
document.putProperties(data)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;
we get to&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val docId = username + &amp;quot;.&amp;quot; + UUID.randomUUID()
val data = mapOf(&amp;quot;type&amp;quot; to &amp;quot;task-list&amp;quot;, &amp;quot;name&amp;quot; to title, &amp;quot;owner&amp;quot; to username)
val mDoc = MutableDocument(docId, data)
db.save(mDoc)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;Updating&lt;/u&gt; existing document looks pretty much the same. You just need to set an existing document id. To create a &lt;code&gt;MutableDocument&lt;/code&gt; from &lt;code&gt;Document&lt;/code&gt; just call &lt;code&gt;document.toMutable()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;u&gt;Reading&lt;/u&gt; a single document also looks the same&lt;/p&gt;
&lt;p&gt;&lt;code&gt;val document = db.getDocument(mDoc.id)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;but to get the properties from the document instead of calling &lt;code&gt;document.properties&lt;/code&gt; you need to call &lt;code&gt;document.toMap()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;u&gt;Deleting&lt;/u&gt; a single document goes from&lt;/p&gt;
&lt;p&gt;&lt;code&gt;document.delete() or document.purge()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;to&lt;/p&gt;
&lt;p&gt;&lt;code&gt;db.delete(document) or db.purge()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The real fun begins when we need to create a query to the database. Instead of calling &lt;code&gt;db.getView(viewName).createQuery()&lt;/code&gt;, we build our query step by step using &lt;strong&gt;&lt;em&gt;QueryBuilder&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val query = db.getView(&amp;quot;list/listByName&amp;quot;).createQuery() 
query.isDescending = false
query.limit = limit

query.run().forEach { result -&amp;gt;
   val doc = result.document /* we can get document or properties */
   /* mapping and other stuff */
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val query = QueryBuilder.select(SelectResult.expression(Meta.id),
                               SelectResult.all())
       .from(DataSource.database(db))
       .where(Expression.property(&amp;quot;type&amp;quot;).equalTo(Expression.string(&amp;quot;task-list&amp;quot;)))
       .orderBy(Ordering.property(&amp;quot;name&amp;quot;).ascending())
       .limit(Expression.intValue(limit))


query.execute().forEach { result -&amp;gt;
   val properties = result.toMap() /* we cannot access the document itself */
   /* mapping and other stuff */
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;This may seem longer at first sight, but remember that in the first example we omitted creating the view.&lt;/p&gt;
&lt;p&gt;The semantics of the query builder is really easy to understand, as it reminds good old SQL &lt;code&gt;SELECT * FROM table_name WHERE id=id_number ORDER BY name ASC LIMIT limit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, there are a few things that need to be pointed out.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;select()&lt;/code&gt; method you’d probably use mostly these three expressions:&lt;/p&gt;
&lt;p&gt;• &lt;code&gt;SelectResult.expression(Meta.id)&lt;/code&gt; - which  will result in returning document id (&lt;em&gt;key&lt;/em&gt; from the database, not the id inside your JSON, if you have one)  &lt;u&gt;under the name id&lt;/u&gt;.&lt;/p&gt;
&lt;p&gt;• &lt;code&gt;SelectResult.all()&lt;/code&gt; - which will result in returning entire JSON document in a form of a Dictionary &lt;u&gt;under the name of your database&lt;/u&gt;.&lt;/p&gt;
&lt;p&gt;• &lt;code&gt;SelectResult.property(&amp;quot;name&amp;quot;)&lt;/code&gt; - which will result in returning a property name from your JSON document.&lt;/p&gt;
&lt;p&gt;Of course you can build more complex expressions using comparators like &lt;code&gt;Expression.greaterThan()&lt;/code&gt; or functions like &lt;code&gt;Function.count()&lt;/code&gt; etc.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Where&lt;/code&gt; expressions can be chained using operators like &lt;code&gt;Expression.and()&lt;/code&gt; or &lt;code&gt;Expression.or()&lt;/code&gt;. You can also chain QueryBuilder with &lt;code&gt;join()&lt;/code&gt; or &lt;code&gt;groupBy()&lt;/code&gt; components.&lt;/p&gt;
&lt;p&gt;For example, the above query will result in a structure like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
  {
         &amp;quot;id&amp;quot;: &amp;quot;todo.123&amp;quot;,
         &amp;quot;todo&amp;quot;: {
               &amp;quot;name&amp;quot;: &amp;quot;Groceries&amp;quot;,
               &amp;quot;owner&amp;quot;: &amp;quot;todo&amp;quot;,
               &amp;quot;type&amp;quot;: &amp;quot;task-list&amp;quot;
         }
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;
One more confusing thing is how Couchbase Lite handles numbers now. If you want to maintain a Double field, it can be serialized (and as a result deserialized) to &lt;a href="https://github.com/couchbase/couchbase-lite-android/issues/1160" target="_blank"&gt;different data types&lt;/a&gt;. If the decimal part is zero, it will be deserialized as &lt;code&gt;Long&lt;/code&gt;. If a number fits in the &lt;code&gt;Float&lt;/code&gt; type, it will be deserialized as&lt;code&gt;Float&lt;/code&gt;. Therefore, if you need a &lt;code&gt;Double&lt;/code&gt;, it would be best to read it from the document map as &lt;code&gt;Number&lt;/code&gt; and then call &lt;code&gt;toDouble()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You also should note that there are no more options to run multiple operations in the transaction. Instead, you can use &lt;code&gt;db.inBatch()&lt;/code&gt; - although it works a bit differently.&lt;/p&gt;
&lt;p&gt;The last thing you need to take into account is that for today (May 2019) Couchbase Lite versions 2.x do not support devices with architecture x86_64. That means they won’t work on these devices, but it also means that you will not be able to write a unit test using eg. Robolectric to test your db code. Instrumentation test, however, will work fine.&lt;/p&gt;
&lt;p&gt;Now, hopefully, you will be able to migrate your database and CRUD operations from Couchbase Lite 1.4 to 2.0. For other examples and features you can look in the &lt;a href="https://docs.couchbase.com/couchbase-lite/2.0/java.html" target="_blank"&gt;official documentation&lt;/a&gt; and dive into some &lt;a href="https://docs.couchbase.com/tutorials/mobile-travel-sample/java/installation/travel-mobile-app.html" target="_blank"&gt;official tutorials&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;We hope you find this article useful, and as always: if you have any comments, feel free to let us know!&lt;/h3&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;About the Author&lt;/strong&gt;&lt;br /&gt; 
&lt;a href="https://www.linkedin.com/in/aleksandra-a-krzemien/" target="_blank"&gt;Aleksandra Krzemień&lt;/a&gt; has profound experience in building all sorts of mobile apps, including those connected with external devices. She is a natural mentor and loves to share her knowledge. Throughout her career, she has been actively engaged in many mentoring programs and IT-related organizations, such as Women in Technology or Google Developers Group. She likes to spend her free time singing and playing board games.&lt;/p&gt;
</description>
      <pubDate>Thu, 23 May 2019 07:12:52 Z</pubDate>
      <a10:updated>2019-05-23T07:12:52Z</a10:updated>
    </item>
  </channel>
</rss>