<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/">
	<channel>
		<title>Enabling Philips Coffee+ Users to Brew Drinks Remotely | Infinum</title>
		<atom:link href="https://infinum.com/blog/how-we-enabled-coffee-users-brew-drinks-remotely/feed/" rel="self" type="application/rss+xml" />
		<link>https://infinum.com/blog/how-we-enabled-coffee-users-brew-drinks-remotely/</link>
		<description>Building digital products</description>
		<lastBuildDate>Wed, 29 Apr 2026 14:21:05 +0000</lastBuildDate>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>

					<item>
				<image>
					<url>29615https://infinum.com/uploads/2022/11/Remote-brewing.webp</url>
				</image>
				<title>Enabling Philips Coffee+ Users to Brew Drinks Remotely</title>
				<link>https://infinum.com/blog/how-we-enabled-coffee-users-brew-drinks-remotely/</link>
				<pubDate>Mon, 07 Nov 2022 13:23:39 +0000</pubDate>
				<dc:creator>Marin Šimić</dc:creator>
				<guid isPermaLink="false">https://infinum.com/?p=29615</guid>
				<description>
					<![CDATA[<p>As part of the 2.0.0 release, we finally launched the most requested and most challenging feature so far – remote brewing.</p>
<p>The post <a href="https://infinum.com/blog/how-we-enabled-coffee-users-brew-drinks-remotely/">Enabling Philips Coffee+ Users to Brew Drinks Remotely</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</description>
				<content:encoded>
					<![CDATA[<div
	class="wrapper"
	data-id="es-121"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-92">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-95"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-93">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-94'
	>
	Coffee+ is the accompanying application for Philips coffee machines that allows users to control certain functions of their machines remotely. The app provides a seamless user experience and offers various ways of interacting with the machine over WiFi. Users can turn their machine on and off, set a timer to turn it on at a specific time, adjust water hardness and standby time. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-98"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-96">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-97'
	>
	As part of the 2.0.0 release, we finally launched the most requested and most challenging feature so far – remote brewing.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-100"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-99'
	>
	This feature allows coffee machine users to choose a specific drink, customize it to their liking, start and track the brewing process without having to physically interact with the machine.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-103"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-101">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-102'
	>
	However, implementing the feature was no simple task. This blog post will discuss a problem we encountered on the way and how we solved it.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-110"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<a	class="card-simple js-card-simple card-simple--is-ad block-card__card-simple card-simple--has-link js-card-simple-link card-simple__content-align--left"
	data-id="es-104"
	 target='_blank' rel='noopener noreferrer' href='https://infinum.com/iot-implementation/'>

	
	
	<div class="card-simple__content">
		<div class="card-simple__heading-wrap">
			<h2	class='typography typography--size-24-text js-typography card-simple__heading'
	data-id='es-105'
	>
	From strategy to deployment, we cover all aspects of IoT development lifecycle. <strong>Download our Guide to Successful IoT Implementation</strong>.</h2>		</div>

		<button	class="btn btn--color-infinum btn--size-small btn--width-default btn__icon-position--right card-simple__btn js-block-card-btn js-card-simple-link"
	data-id="es-107"
	 tabindex='-1'>
		<div class="btn__inner">
					<div	class='typography typography--size-none js-typography btn__label'
	data-id='es-108'
	>
	Learn more</div>		
		<i
	class="icon btn__icon icon--size-16 icon--scale-100"
	 aria-hidden='true' data-name='arrow-right-16' data-id='es-109'>
	<svg fill='none' height='16' viewBox='0 0 17 16' width='17' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'><g stroke='currentColor' stroke-width='2'><path d='m.5 7.99999 14 .00001'/><path d='m9.23352 2.7251 5.97848 5.97852'/><path d='m9.23352 13.2744 5.97848-5.9785'/></g></svg></i>	</div>
	</button>	</div>
</a>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-113"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-111">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-112'
	>
	The elusive brewing status</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-116"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-114">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-115'
	>
	The remote brewing flow is far from simple. It required adding new activities as well as updating some old ones. Apart from the standard happy flow, we had to implement an error-handling flow as well as various checks and handles throughout the brewing process.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-119"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-117">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-118'
	>
	The biggest challenge we encountered during implementation was keeping track of the brewing status in different activities and navigation graphs. Also, during the brewing process the machine couldn’t be a single source of truth for the current state of brewing due to the lack of emitted properties. This means that simply renewing the subscription and reading the properties wouldn’t suffice if we wanted to determine the latest state. If we wanted to show a snackbar at the bottom of <code>DashboardActivity</code> immediately after remote brewing is canceled, reading machine properties wouldn&#8217;t provide enough information for us to do it.</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-124"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-122"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-123">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2022/11/the_problem_source-1400x1293.webp				media='(max-width: 699px)'
				type=image/webp								height="1293"
												width="1400"
				 />
												<img
					src="https://infinum.com/uploads/2022/11/the_problem_source.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1940"
															width="2100"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-196"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-125">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-128"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-126">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-127'
	>
	As you can see in the example above, once brewing is canceled by calling <code>CancelBrewingUseCase</code> and we close <code>BrewingActivity</code>, the machine state remains at <code>BREWING</code> for some time until the whole process is completed. So there is no real way of determining if the brewing process was canceled just by observing machine properties.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-131"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-129">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-130'
	>
	On top of that, we wanted our logic to be handled in one component for easier logging and testing, as well as reducing the amount of repetitive code. Since there is always a chance we will expand upon this feature sometime in the future, we also wanted our code to be clean and scalable.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-134"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-132">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-133'
	>
	Envisioning a solution</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-137"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-135">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-136'
	>
	We’ve defined the problem, and now we can start looking into potential solutions. There are several aspects we’ll need to consider.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-140"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-138">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-139'
	>
	Scope</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-143"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-141">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-142'
	>
	Because the brewing process spans multiple activities, we cannot use a shared <code>ViewModel</code>, meaning we need a separate component independent of any activity lifecycle. We can conclude that we need a component with a scope that lasts throughout the entire remote brewing flow.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-146"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-144">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-145'
	>
	States</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-149"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-147">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-148'
	>
	How do we present the state of the brewing process? The machine can go through various states during the process. For example, before brewing is started, the machine is considered to be in an <code>IdleState</code>. When it starts brewing, the machine switches to <code>ProgressState</code> and the updated progress value should be shown in the app UI. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-151"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-150'
	>
	If an error occurs during brewing, for example, someone pulls the drip tray out, the machine switches to <code>ErrorHandlingState</code>, and user action is expected. Some of the states can be observed directly from the values emitted by the machine, but some are more dependent on the user&#8217;s interaction with the app. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-154"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-152">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-153'
	>
	As mentioned, when a user decides to cancel brewing, we should switch to <code>CancelledState</code>, which cannot be determined from the machine properties. We can conclude that brewing should be reflected in various states. These states can change depending on the properties observed from the machine or from the user&#8217;s interaction with the application.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-157"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-155">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-156'
	>
	State machine</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-160"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-158">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-159'
	>
	All of these requirements point towards the finite-state machine or FSM. FSM is a mathematical model of computation, an abstract machine that can be in exactly one of a finite number of states at once. FSM can then change states depending on the current state and input. These are called transitions and in our case, inputs that will trigger transitions can either come from the machine side (a change of value observed) or from a user’s interaction with the application.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-163"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-161">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-162'
	>
	Therefore, to determine where the input came from, we want to make a lifecycle-independent state machine component with a single current state that will always reflect the state of remote brewing.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-166"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-164">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-165'
	>
	Implementation</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-169"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-167">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-168'
	>
	Now that we’ve defined what our solution requires, we can dive into the implementation in more detail.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-172"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-170">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-171'
	>
	State</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-175"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-173">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-174'
	>
	First let’s see how a state is defined through the <code>BaseState</code> interface:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-177"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">interface BaseState</span><span class="token" style="color: #6f42c1;">&lt;State : BaseState&lt;State, Output, Action&gt;, Output, Action&gt;</span><span class="token"> {
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">suspend</span><span class="token"> </span><span class="token">fun onAction</span><span class="token">(action</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Action</span><span class="token">): </span><span class="token" style="color: #6f42c1;">S</span><span class="token" style="color: #6f42c1;">tate</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">fun toOutput</span><span class="token">()</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Output</span><span class="token">
</span></span><span class="line"><span class="token">}
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-180"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-178">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-179'
	>
	If we look at the definition of <code>BaseState</code>, we can notice two generic types, <code>Output</code> and <code>Action</code>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-183"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-181">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-182'
	>
	<li><code>Output</code> is a model that holds state properties. By emitting an <code>Output</code> rather than the whole <code>State</code> we protect state values and functions from other components.</li><li><code>Action</code> represents inputs that lead to transitions. Each state can handle a certain <code>Action</code> through the call of <code>onAction</code> function and return a new <code>State</code> (or itself). That way states perform transitions by handling actions.</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-186"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-184">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-185'
	>
	Now let&#8217;s take a look at a simplified example of <code>ProgressState</code>:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-188"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">class ProgressState</span><span class="token">(
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token">val </span><span class="token">progress</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Int</span><span class="token"> = </span><span class="token" style="color: #6f42c1;">0</span><span class="token">
</span></span><span class="line"><span class="token">) </span><span class="token">: </span><span class="token" style="color: #6f42c1;">BaseBrewingState</span><span class="token"> </span><span class="token">{
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">override</span><span class="token"> </span><span class="token">fun toOutput</span><span class="token">()</span><span class="token">: </span><span class="token" style="color: #6f42c1;">BrewingOutput</span><span class="token"> =
</span></span><span class="line"><span class="token">        BrewingOutput</span><span class="token">.ProgressState</span><span class="token">(
</span></span><span class="line"><span class="token">            progress </span><span class="token" style="color: #d73a49;">=</span><span class="token"> progress
</span></span><span class="line"><span class="token">        )
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">override</span><span class="token"> </span><span class="token" style="color: #d73a49;">suspend</span><span class="token"> </span><span class="token">fun onAction</span><span class="token">(action</span><span class="token">: </span><span class="token" style="color: #6f42c1;">BrewingAction</span><span class="token">): </span><span class="token" style="color: #6f42c1;">B</span><span class="token" style="color: #6f42c1;">aseBrewingState</span><span class="token"> =
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #d73a49;">when</span><span class="token"> (action) {
</span></span><span class="line"><span class="token">            </span><span class="token" style="color: #d73a49;">is</span><span class="token"> BrewingAction.UpdateProgress </span><span class="token" style="color: #d73a49;">-&gt;</span><span class="token"> action</span><span class="token">.handle</span><span class="token">()
</span></span><span class="line"><span class="token">            </span><span class="token" style="color: #d73a49;">is</span><span class="token"> BrewingAction.StartErrorHandling </span><span class="token" style="color: #d73a49;">-&gt;</span><span class="token"> action</span><span class="token">.handle</span><span class="token">()
</span></span><span class="line"><span class="token">            </span><span class="token" style="color: #d73a49;">is</span><span class="token"> BrewingAction.CancelBrewing </span><span class="token" style="color: #d73a49;">-&gt;</span><span class="token"> action</span><span class="token">.handle</span><span class="token">()
</span></span><span class="line"><span class="token">            </span><span class="token" style="color: #d73a49;">else</span><span class="token"> </span><span class="token" style="color: #d73a49;">-&gt;</span><span class="token"> </span><span class="token" style="color: #005cc5;">this</span><span class="token">
</span></span><span class="line"><span class="token">        }
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token">fun BrewingAction.UpdateProgress</span><span class="token">.handle</span><span class="token">()</span><span class="token">: </span><span class="token" style="color: #6f42c1;">BaseBrewingState</span><span class="token"> = </span><span class="token" style="color: #6f42c1;">Pr</span><span class="token">ogressState</span><span class="token">(updatedProgress)
</span></span><span class="line"><span class="token">    
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token">fun BrewingAction.StartErrorHandling</span><span class="token">.handle</span><span class="token">()</span><span class="token">: </span><span class="token" style="color: #6f42c1;">BaseBrewingState</span><span class="token"> = </span><span class="token" style="color: #6f42c1;">Er</span><span class="token">rorState</span><span class="token">()
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token">fun BrewingAction.CancelBrewing</span><span class="token">.handle</span><span class="token">()</span><span class="token">: </span><span class="token" style="color: #6f42c1;">BaseBrewingState</span><span class="token"> = </span><span class="token" style="color: #6f42c1;">Ca</span><span class="token">ncelledState</span><span class="token">()
</span></span><span class="line"><span class="token">}
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-191"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-189">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-190'
	>
	Function <code>toOutput</code> is implemented and returns <code>BrewingOutput.ProgressState</code> which holds the latest <code>progress</code> value. The other function <code>onAction</code> handles three types of <code>BrewingAction</code>, otherwise the same state is returned and no transitions are made. In case the <code>UpdateProgress</code> action is received, a new <code>ProgressState</code> is returned with updated <code>progress</code> value. Action <code>StartErrorHandling</code> will return <code>ErrorState</code> and the last handled action <code>CancelBrewing</code> will return <code>CancelledState</code>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-194"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-192">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-193'
	>
	If we wanted to visualize these state transitions with a diagram, they would look something like this:</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-199"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-197"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-198">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2022/11/Progress-state-diagram-1400x588.webp				media='(max-width: 699px)'
				type=image/webp								height="588"
												width="1400"
				 />
								
			<source
				srcset=https://infinum.com/uploads/2022/11/Progress-state-diagram-2400x1008.webp				media='(max-width: 1199px)'
				type=image/webp								height="1008"
												width="2400"
				 />
												<img
					src="https://infinum.com/uploads/2022/11/Progress-state-diagram.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1150"
															width="2738"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-253"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-200">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-203"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-201">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-202'
	>
	If our <code>ProgressState</code> receives the <code>CancelBrewing</code> action from a user’s interaction with the UI it will immediately transition to <code>CancelledState</code>. Otherwise, if the brewing progress property changes from the machine side, <code>ProgressState</code> will receive the <code>UpdateProgress</code> action and transition to a new <code>ProgressState</code> with updated <code>progress</code> value. Finally, if any errors are observed from the machine side, the <code>StartErrorHandling</code> action is received and <code>ProgressState</code> transitions to <code>ErrorState</code> where it will wait for the user to resolve the machine error.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-206"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-204">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-205'
	>
	State machine</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-209"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-207">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-208'
	>
	Now that we have a good understanding of how states and transitions work, the only thing left is to implement a state machine. Following is an example of <code>BaseStateMachine</code>:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-211"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">abstract</span><span class="token"> </span><span class="token">class BaseStateMachine</span><span class="token" style="color: #6f42c1;">&lt;State : BaseState&lt;State, Output, Action&gt;, Output, Action&gt;</span><span class="token"> {
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">protected</span><span class="token"> </span><span class="token" style="color: #d73a49;">abstract</span><span class="token"> </span><span class="token">val </span><span class="token">scope</span><span class="token">: </span><span class="token" style="color: #6f42c1;">CoroutineScope</span><span class="token">
</span></span><span class="line"><span class="token">    
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">abstract</span><span class="token"> </span><span class="token">val </span><span class="token">stateFlow</span><span class="token">: </span><span class="token" style="color: #6f42c1;">StateFlow</span><span class="token">&lt;</span><span class="token" style="color: #6f42c1;">Output</span><span class="token">&gt;
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">protected</span><span class="token"> </span><span class="token" style="color: #d73a49;">abstract</span><span class="token"> </span><span class="token">val </span><span class="token">currentState</span><span class="token">: </span><span class="token" style="color: #6f42c1;">State</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">abstract</span><span class="token"> </span><span class="token" style="color: #d73a49;">suspend</span><span class="token"> </span><span class="token">fun update</span><span class="token">(action</span><span class="token">: </span><span class="token" style="color: #6f42c1;">Action</span><span class="token">)
</span></span><span class="line"><span class="token">}
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-214"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-212">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-213'
	>
	The first value to implement is the <code>scope</code>. As mentioned earlier, we want our state machine to have its own independent scope. In this example we are using <code>CoroutineScope</code>:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-216"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">override</span><span class="token"> </span><span class="token">val </span><span class="token">scope </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token">CoroutineScope</span><span class="token">(dispatcher </span><span class="token" style="color: #d73a49;">+</span><span class="token"> </span><span class="token">SupervisorJob</span><span class="token">())
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-219"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-217">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-218'
	>
	When initializing <code>CoroutineScope</code>, we are using an injected dispatcher combined with <code>SupervisorJob</code>. That way we can be sure that failing jobs will not cancel the whole state machine process. Inside the <code>scope</code> the state machine can observe machine properties, perform actions, and make state changes. If we wanted to observe the brewing progress flow and perform actions on each updated value, we would do it inside the mentioned <code>scope</code>:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-221"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token"> </span><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token">fun observeProgress</span><span class="token">() </span><span class="token" style="color: #d73a49;">=</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">observeBrewingProgress</span><span class="token">()</span><span class="token">.onEach </span><span class="token">{
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> update state</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> log tracked progress</span><span class="token">
</span></span><span class="line"><span class="token">    }</span><span class="token">.launchIn</span><span class="token">(scope)
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-224"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-222">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-223'
	>
	The second value implemented is the <code>stateFlow</code>, which is the output of the state machine. Other components can subscribe to <code>stateFlow</code> changes and observe the state of brewing. Here is an example of <code>stateFlow</code> used in the implementation with its initial value set to <code>BrewingOutput.IdleState</code>:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-226"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token"> </span><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token">val </span><span class="token">mutableStateFlow </span><span class="token" style="color: #d73a49;">=</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token">MutableStat</span><span class="token">eFlow&lt;</span><span class="token" style="color: #6f42c1;">BrewingOutput</span><span class="token">&gt;</span><span class="token">(BrewingOutput.IdleState)
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #d73a49;">override</span><span class="token"> </span><span class="token">val </span><span class="token">stateFlow</span><span class="token">: </span><span class="token" style="color: #6f42c1;">StateFlow</span><span class="token">&lt;</span><span class="token" style="color: #6f42c1;">BrewingOutput</span><span class="token">&gt; =</span><span class="token"> mutableStateFlow
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-229"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-227">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-228'
	>
	The next value implemented is <code>currentState</code>, which holds the reference to the latest state the state machine was in:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-231"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">override</span><span class="token"> </span><span class="token">var </span><span class="token">currentState</span><span class="token">: </span><span class="token" style="color: #6f42c1;">BaseBrewingState</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token">IdleState</span><span class="token">()
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token" style="color: #d73a49;">set</span><span class="token">(</span><span class="token" style="color: #d73a49;">value</span><span class="token">) {`
</span></span><span class="line"><span class="token">         </span><span class="token" style="color: #d73a49;">field</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #d73a49;">value</span><span class="token">
</span></span><span class="line"><span class="token">         mutableStateFlow</span><span class="token">.tryEmit</span><span class="token">(</span><span class="token" style="color: #d73a49;">value</span><span class="token">.toOutput</span><span class="token">())
</span></span><span class="line"><span class="token">         </span><span class="token">log</span><span class="token">(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">New state set: </span><span class="token" style="color: #032f62;">${</span><span class="token" style="color: #d73a49;">value</span><span class="token" style="color: #032f62;">.toOutput</span><span class="token" style="color: #032f62;">()</span><span class="token" style="color: #032f62;">}</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)
</span></span><span class="line"><span class="token">     }
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-234"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-232">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-233'
	>
	Initially, the <code>currentState</code> is set to <code>IdleState</code>. The value setter is marked as <code>private</code> to ensure encapsulation and prevent other components from altering the state. Once a new state is set, the state output value is emitted to the <code>mutableStateFlow</code> to notify all of the observing components about the latest state change. This state change is also logged.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-237"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-235">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-236'
	>
	The last to implement was the <code>update</code> function, which acts as the only input to the state machine:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-239"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-kotlin github-light" data-language="kotlin" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token"> </span><span class="token" style="color: #d73a49;">override</span><span class="token"> </span><span class="token" style="color: #d73a49;">suspend</span><span class="token"> </span><span class="token">fun update</span><span class="token">(action</span><span class="token">: </span><span class="token" style="color: #6f42c1;">BrewingAction</span><span class="token">)</span><span class="token"> {
</span></span><span class="line"><span class="token">     currentState </span><span class="token" style="color: #d73a49;">=</span><span class="token"> currentState</span><span class="token">.onAction</span><span class="token">(action)
</span></span><span class="line"><span class="token"> }
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-242"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-240">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-241'
	>
	Certain app components can use this function to interact with the brewing process. Once this function is invoked with a specific <code>action</code>, the result of <code>currentState.onAction(action)</code> is going to be the next <code>currentState</code>. For example, if our <code>currentState</code> is <code>ProgressState</code> from the previous chapter, once <code>update(BrewingAction.CancelBrewing)</code> is called, the resulting <code>CancelledState</code> will be the new <code>currentState</code> and all of the observing components will be notified of the change through <code>stateFlow</code>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-245"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-243">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-244'
	>
	Architecture</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-248"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-246">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-247'
	>
	By now we should have a deeper understanding of the state machine. Now let&#8217;s see how it blends into our app’s architecture. As mentioned before, the state machine has its own scope, meaning it differs from any other domain component (UseCase, Repository, Interactor). On top of that, we would also like to use these components for performing actions inside the StateMachine. For that reason, StateMachine is like a bridge layer between the UI and the domain layers.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-251"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-249">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-250'
	>
	Here’s an example of the way state machine interacts with the components from both UI and domain layers:</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-256"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-254"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-255">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2022/11/state_machine_architecture_source-1400x1537.webp				media='(max-width: 699px)'
				type=image/webp								height="1537"
												width="1400"
				 />
												<img
					src="https://infinum.com/uploads/2022/11/state_machine_architecture_source.webp"
					class="image__img block-media__image-img"
					alt=""
										height="2240"
															width="2040"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-265"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-257">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-260"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-258">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-259'
	>
	The example above shows a standard brewing navigation flow. First, when the drink parameters are defined, brewing is started inside <code>AdjustDrinkViewModel</code> by sending <code>BrewingAction.StartBrewing(drink)</code>. <code>BrewingStateMachine</code> handles this action by calling <code>StartBrewingUseCase</code> and performs state transition to <code>ProgressState</code>. This state is updated by observing <code>ObserveBrewingProgressUseCase</code>, with each progress change from the machine being emitted as <code>BrewingOutput.ProgressState(progress)</code>. The output is observed inside the <code>BrewingProgressViewModel</code> and the UI is updated accordingly. If the user decides to cancel brewing, <code>BrewingAction.CancelBrewing</code> is sent inside <code>CancelBrewingViewModel</code> and <code>BrewingActivity</code> is finished. Upon receiving this action, <code>BrewingStateMachine</code> cancels the brewing process by calling <code>CancelBrewingUseCase</code> and transitions to <code>CancelledState</code>. This new <code>BrewingOutput.CancelledState</code> is observed inside the <code>DashboardViewModel</code> and a snackbar is shown notifying the user that brewing was canceled.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-263"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-261">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-262'
	>
	State machine and onwards</h2></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-268"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-266"
	 data-media-type='video'>

	<div class="video__wrapper" data-id="es-267">
		<video
		class="video block-media__video js-video js-block-media-video video--cursor-takeover-use"
		 loop autoplay playsinline muted preload='metadata'>
		<source  src='https://infinum.com/uploads/2022/11/state_machine_flow.mp4' type='video/mp4' />	</video>
	</div></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-286"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-269">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-272"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-270">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-271'
	>
	We successfully added a much-anticipated feature for Coffee+ users. Enabling remote brewing was not a simple implementation, but we managed to tackle the challenges involved.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-275"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-273">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-274'
	>
	Working our way through the implementation, we got a very close look at the state machine. Let’s summarize some of its benefits:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-278"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-276">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-277'
	>
	<li>State machine acts as a single point of reference for controlling and observing the brewing process and can be used as such from any ViewModel inside the application.</li><li>State machine is a lifecycle-independent component that has its own scope, meaning it will persist regardless of activity changes like in the example above.</li><li>State machines are easily scalable by simply adding new states and actions, with states handling transitions without accumulating code inside the state machine.</li><li>Most of the logic is stored inside the state machine component, preventing us from writing repetitive code elsewhere and having to test it individually.</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-281"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-279">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-280'
	>
	The remote brewing feature is now fully functional, and our clean and scalable code will allow us to build upon it even further in the future. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-284"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-282">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-283'
	>
	<em>To find out what else we can do in the Internet of Things realm, check out our <a href="https://infinum.com/custom-iot-solutions/" target="_blank" rel="noreferrer noopener">IoT solutions page</a>.</em></p></div>	</div>
</div>
</div>		</div>
	</div><p>The post <a href="https://infinum.com/blog/how-we-enabled-coffee-users-brew-drinks-remotely/">Enabling Philips Coffee+ Users to Brew Drinks Remotely</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</content:encoded>
			</item>
		
	</channel>
</rss>