<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>gryt.io on Gryt</title><link>https://gryt.io/</link><description>Recent content in gryt.io on Gryt</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>robin@gryt.io (Robin Penea)</managingEditor><webMaster>robin@gryt.io (Robin Penea)</webMaster><lastBuildDate>Thu, 02 Apr 2026 17:27:05 +0200</lastBuildDate><atom:link href="https://gryt.io/index.xml" rel="self" type="application/rss+xml"/><item><title>The miracle of AI agent-assisted learning</title><link>https://gryt.io/posts/miracle-of-ai-agent-assisted-learning/</link><pubDate>Thu, 02 Apr 2026 17:27:05 +0200</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/posts/miracle-of-ai-agent-assisted-learning/</guid><description>&lt;p>Everybody&amp;rsquo;s talking about the impact of AI code generation. It&amp;rsquo;s a big deal. But I found incredible value on the other side: &lt;strong>AI assisted learning&lt;/strong>. Here&amp;rsquo;s my blueprint.&lt;/p></description><content>&lt;p>Everybody&amp;rsquo;s talking about the impact of AI code generation. It&amp;rsquo;s a big deal. But I found incredible value on the other side: &lt;strong>AI assisted learning&lt;/strong>. Here&amp;rsquo;s my blueprint.&lt;/p>
&lt;h2 id="the-situation">The situation&lt;/h2>
&lt;p>There&amp;rsquo;s a specific category of code where I&amp;rsquo;m happy to delegate the implementation to an AI: I&amp;rsquo;m proficient with the language and framework, the feature is well-scoped, edge cases are discussed, and the plan is solid. For any other situation, I strongly disagree with the complete AI delegation. For a toy-project, sure. For a product&amp;hellip; Not so sure.&lt;/p>
&lt;p>To put it simply: by delegating a non-trivial/non-mastered code, I&amp;rsquo;m losing touch with the project. More specifically, &lt;strong>I cannot feel the rough edges of the structure anymore&lt;/strong>. Refactoring becomes a prayer to the LLM gods. I hope it doesn&amp;rsquo;t fuck up, because I won&amp;rsquo;t be able to unfuck it.&lt;/p>
&lt;p>The situation I needed to resolve is the following:&lt;/p>
&lt;blockquote>
&lt;p>When I&amp;rsquo;m still actively learning, how can I leverage the incredible power of an AI agent?&lt;/p>
&lt;/blockquote>
&lt;p>This approach emerged over months of learning new languages, tech stacks, and more.&lt;/p>
&lt;h2 id="tldr">TLDR&lt;/h2>
&lt;p>You can download the &lt;a href="https://gist.github.com/synapticvoid/cc0d61003d968a365fa5564fdba5127d">SKILL.md&lt;/a> and start developing with this approach by typing:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">/guide I want to implement feature X
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="the-setup">The setup&lt;/h2>
&lt;p>I&amp;rsquo;ll implement an LRU (Least Recently Used) cache in Python to illustrate my approach. Keep in mind that &lt;strong>it can be applied to virtually any situation&lt;/strong>. The setup is the following, in split windows:&lt;/p>
&lt;ul>
&lt;li>Open an AI Agent (Claude Code, Codex, OpenCode, amp, etc)&lt;/li>
&lt;li>Open your favorite code editor&lt;/li>
&lt;/ul>
&lt;p>In the agent window, write the following prompt:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-mysql" data-lang="mysql">&lt;span class="line">&lt;span class="cl">&lt;span class="n">I&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">want&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">to&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">develop&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">an&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LRU&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Your&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">role&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">is&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">to&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">guide&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">me&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">through&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">the&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">implementation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">Let&lt;/span>&lt;span class="s1">&amp;#39;s discuss the plan, I will confirm when it&amp;#39;&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">ready&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">### Keywords
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">**`&lt;/span>&lt;span class="n">hint&lt;/span>&lt;span class="o">`**&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="err">—&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">I&lt;/span>&lt;span class="s1">&amp;#39;m stuck. Give one targeted nudge: a single pointer, help.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">Do not review the full implementation.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">**`check`** — I want a review of my work on the current step:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">1. Read the changed files.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">2. Critique: correctness, idioms, edge cases.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">3. Suggest specific improvements if needed.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">4. If the step is complete and solid: confirm and present the next step.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">5. If not: give focused feedback and wait for another `check`.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">## Hard constraints:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">- **Never write, edit, or create code files** unless the user explicitly asks.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">- **Never give a full implementation** unless explicitly asked — guide, don&amp;#39;&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">solve&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">You&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">may&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">read&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">files&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">the&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">codebase&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">and&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tests&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">CLI&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">commands&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;strong>I DO 100% OF THE IMPLEMENTATION&lt;/strong>. The goal of the AI is to provide guidance, not replace me&lt;/li>
&lt;li>&lt;strong>The discussion of the plan is absolutely fundamental&lt;/strong>. This improves the quality of the Agent output, and clarifies your ideas. More on this below&lt;/li>
&lt;li>I keep the &lt;em>&amp;ldquo;unless the user explicitly asks&lt;/em>&amp;rdquo; to still delegate some mechanical refactoring&lt;/li>
&lt;li>&lt;code>hint&lt;/code> and &lt;code>check&lt;/code> make it fast to type, and act as checkpoints&lt;/li>
&lt;/ul>
&lt;h2 id="the-conversation">The conversation&lt;/h2>
&lt;p>Here&amp;rsquo;s what the conversation looked like after my initial prompt (edited for brevity). I intentionally added clarifying questions to show you how we can shape the plan:&lt;/p>
&lt;h3 id="-agent">🤖 Agent&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">LRU Cache — Implementation Plan
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Before we start, what language do you want to implement this in?
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Here&amp;#39;s the plan we&amp;#39;ll follow. Review and confirm:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Step 1 — Data structures: hashmap for O(1) lookup + doubly linked list for usage order. Define Node and LRUCache with sentinel head/tail.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Step 2 — List helpers: remove(node) and insert_front(node).
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Step 3 — get(key): hit → move to front + return value. Miss → return -1.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Step 4 — put(key, value): update or insert at front; if at capacity, evict the tail sentinel&amp;#39;s predecessor first.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Step 5 — Edge cases: capacity 1, key overwrite, get after eviction.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Does this plan look good, or do you want to adjust anything (language, structure, scope)?
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="-me">👨🏻‍🦱 Me&lt;/h3>
&lt;blockquote>
&lt;p>I will do the implementation in Python. We can use dict for the hashmap, but what could we we use for the
doubly linked list?&lt;/p>
&lt;/blockquote>
&lt;h3 id="-agent-1">🤖 Agent&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Python doesn&amp;#39;t have a built-in doubly linked list, so you have two options:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1. Roll your own Node class — [...] gives you full control.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2. Use collections.OrderedDict — it hides all the interesting mechanics.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> For learning purposes, I&amp;#39;d recommend option 1. It forces you to implement the list operations yourself, which
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> is the whole point of the exercise.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Ready to start with Step 1?
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="-me-1">👨🏻‍🦱 Me&lt;/h3>
&lt;blockquote>
&lt;p>I&amp;rsquo;d rather start with &lt;code>collections.OrderedDict&lt;/code> to have a good idea of the general shape, write the tests, and
then implement the custom &lt;code>Node&lt;/code>. We will use those test for non regression. Update the plan.&lt;/p>
&lt;/blockquote>
&lt;h3 id="-agent-2">🤖 Agent&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Good approach. Updated plan:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Step 1 — Implement with OrderedDict
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Build LRUCache using collections.OrderedDict. Methods: get(key) and put(key, value).
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - move_to_end() handles the &amp;#34;mark as recently used&amp;#34; logic
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - Eviction is just popping from one end
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Step 2 — Write tests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Cover: basic get/put, capacity enforcement, eviction order, overwrite existing key, capacity 1, get after
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> eviction.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Step 3 — Re-implement with custom Node
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Replace the OrderedDict internals with your own doubly linked list + plain dict. The class interface stays
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> identical — tests should pass unchanged.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Step 4 — Verify non-regression
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Run the same test suite against the new implementation.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Confirm and I&amp;#39;ll walk you through Step 1.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="-me-2">👨🏻‍🦱 Me&lt;/h3>
&lt;blockquote>
&lt;p>Plan confirmed. Write it to &lt;code>plan.md&lt;/code> with the instructions and checklist that you maintain for each step. Add a notes section that will be updated with learning I had along the way.&lt;/p>
&lt;/blockquote>
&lt;h3 id="-agent-3">🤖 Agent&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Step 1 — Implement with OrderedDict
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Create a file lru_cache.py with a class LRUCache that has:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - __init__(self, capacity: int)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - get(self, key: int) -&amp;gt; int — return the value or -1 if not found
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - put(self, key: int, value: int) -&amp;gt; None — insert or update; evict LRU if over capacity
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Two hints to get you started:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - OrderedDict.move_to_end(key) moves a key to the right (most recent) by default
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - OrderedDict.popitem(last=False) pops from the left (least recent)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Give it a go — use check when ready.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="-me-3">👨🏻‍🦱 Me&lt;/h3>
&lt;blockquote>
&lt;p>check&lt;/p>
&lt;/blockquote>
&lt;p>And repeat until the feature is done!&lt;/p>
&lt;h2 id="unlocking-the-potential">Unlocking the potential&lt;/h2>
&lt;p>This approach has several benefits:&lt;/p>
&lt;ol>
&lt;li>You still develop, but the Agent is present all along to guide/respond to your questions.&lt;/li>
&lt;li>If the task spans more than a couple of hours, you will likely be interrupted. &lt;strong>&lt;code>plan.md&lt;/code> is the shared state with the LLM&lt;/strong>. Resume tomorrow by prompting &lt;code>Read plan.md and let's continue&lt;/code>.&lt;/li>
&lt;li>You can switch models without starting from scratch.&lt;/li>
&lt;li>Commit the &lt;code>plan.md&lt;/code> file! When you&amp;rsquo;re finished, delete it and carry on.&lt;/li>
&lt;li>If you start a new feature, reset the context window (&lt;code>/new&lt;/code>, &lt;code>/clear&lt;/code> or whatever) and start from scratch. Same process.&lt;/li>
&lt;/ol>
&lt;p>You &lt;strong>absolutely&lt;/strong> should adapt the plan granularity to your level of proficiency. I identified the following levels:&lt;/p>
&lt;h3 id="level-1">&lt;code>Level 1&lt;/code>&lt;/h3>
&lt;p>You have no idea of what&amp;rsquo;s going on. You&amp;rsquo;re discovering something totally new. The best example is when I follow a tutorial, and I&amp;rsquo;m totally unable to predict what should be done next or how. &lt;strong>Here the plan should be very detailed, to guide you almost line by line.&lt;/strong> It&amp;rsquo;s OK to be dumb, the goal is to level up.&lt;/p>
&lt;h3 id="level-2">&lt;code>Level 2&lt;/code>&lt;/h3>
&lt;p>You&amp;rsquo;re starting to &lt;em>feel&lt;/em> the direction of the development. It&amp;rsquo;s still foggy in your head, but there&amp;rsquo;s something. If the Agent tells you to implement X, Y and you have a (vague) idea of how&amp;hellip; &lt;strong>Share it with the agent!&lt;/strong>&lt;/p>
&lt;blockquote>
&lt;p>To implement X, Y, I&amp;rsquo;m guessing that I should refactor &lt;code>foo.py&lt;/code> and reorder &lt;code>bar()&lt;/code>. Is it right?&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>This is where real mastery happens&lt;/strong>. Form a hypothesis, test it, reassess. That&amp;rsquo;s the key to reach level 3.&lt;/p>
&lt;h3 id="level-3">&lt;code>Level 3&lt;/code>&lt;/h3>
&lt;p>Your opinions about what and how things should be done are getting stronger. You start to challenge the Agent&amp;rsquo;s guidance. This is a great place to be. The plan should start to be more abstract. If it&amp;rsquo;s too detailed, it will rob you of the hard mental training needed to level up. &lt;strong>I&amp;rsquo;ve found myself discussing on a tangent with the Agent for 2h&lt;/strong> because I noticed something in the implementation while coding.&lt;/p>
&lt;p>If you&amp;rsquo;re not already a master of the tech, this step is impossible to explore without writing the code yourself.&lt;/p>
&lt;h3 id="level-4">&lt;code>Level 4&lt;/code>&lt;/h3>
&lt;p>The stack is your home. The goal is only to have a copilot to delegate some mechanical tasks, but the implementation is still yours. The plan should only serve as a global reminder, but it should almost fit in your head. The Agent must also have it with &lt;code>plan.md&lt;/code> :).&lt;/p>
&lt;h2 id="whats-next">What&amp;rsquo;s next?&lt;/h2>
&lt;p>I tested this approach on several topics where I wanted to learn:&lt;/p>
&lt;ul>
&lt;li>Containers orchestration&lt;/li>
&lt;li>Learning a new programming language (Zig)&lt;/li>
&lt;li>Learning advanced Programming Language concepts: type checker, compilers, etc&lt;/li>
&lt;/ul>
&lt;p>It felt like a boon. In a day, I absorbed a week&amp;rsquo;s worth of Docker Swarm concepts. In a couple of weeks, I was writing idiomatic Zig, something that would have taken months on my own. But the real point is this:&lt;/p>
&lt;h3 id="i-learned">I learned.&lt;/h3>
&lt;h3 id="im-now-able-to-understand-and-do-more-the-ai-only-made-it-possible-faster">I&amp;rsquo;m now able to understand and do more. The AI only made it possible &lt;em>faster&lt;/em>.&lt;/h3>
&lt;p>I did not delegate the effort of writing, understanding, forming a hypothesis. The Agent gave me instant access to a &lt;strong>personalized, always available&lt;/strong> teacher. And as a teacher myself, this is so gratifying to continue my learning journey.&lt;/p>
&lt;p>You can download the &lt;a href="https://gist.github.com/synapticvoid/cc0d61003d968a365fa5564fdba5127d">SKILL.md&lt;/a> and start developing with this approach by typing:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">/guide I want to implement feature X
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content></item><item><title>SIMD implementation and Kotlin checkcast incompatibility</title><link>https://gryt.io/til/2026-03-12-simd-kotlin-checkcast-incompatibility/</link><pubDate>Thu, 12 Mar 2026 08:50:56 +0100</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/til/2026-03-12-simd-kotlin-checkcast-incompatibility/</guid><description>&lt;p>I played around with SIMD in Kotlin inside a multi-threaded Mandelbrot generator. &lt;strong>My implementation was ~40% slower than the full scalar approach&lt;/strong>. What happened?&lt;/p></description><content>&lt;p>I played around with SIMD in Kotlin inside a multi-threaded Mandelbrot generator. &lt;strong>My implementation was ~40% slower than the full scalar approach&lt;/strong>. What happened?&lt;/p>
&lt;h2 id="simd">SIMD&lt;/h2>
&lt;p>If you don&amp;rsquo;t know &lt;a href="https://fr.wikipedia.org/wiki/Single_instruction_multiple_data">SIMD instructions&lt;/a>, here&amp;rsquo;s a &lt;em>very&lt;/em> short summary. Say you want to multiply 2 lists:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">6&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Multiply each element&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1*5, 2*6, 3*7, 4*8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">res&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With SIMD, you can pack the 4 values into a wide CPU register, and multiply &lt;strong>with a single instruction&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">numpy&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">np&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">array&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">float32&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">array&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">6&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">float32&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># SIMD here, the whole multiplication was done in a single instruction&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">b&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The idea is to execute the same &lt;em>operation&lt;/em> with variable &lt;em>operands&lt;/em>. Pure data parallelisation.&lt;/p>
&lt;h2 id="in-kotlin">In Kotlin&lt;/h2>
&lt;p>I experimented with the &lt;a href="https://openjdk.org/jeps/508">Vector API&lt;/a> inside a multi-threaded Mandelbrot generator. The perfect playground: the computation of each pixel is independent, and completely mathematical.&lt;/p>
&lt;p>After spending (a long) time converting my scalar code to SIMD, I benchmarked it. &lt;strong>Roughly 40% SLOWER 🤯&lt;/strong>. I dug around, and found this &lt;a href="https://netflixtechblog.com/optimizing-recommendation-systems-with-jdks-vector-api-30d2830401ec">Netflix blog post&lt;/a>. The short version is: &lt;strong>if you are in Kotlin, you should write your SIMD operations in Java&lt;/strong>.&lt;/p>
&lt;p>Why?&lt;/p>
&lt;p>If you write this in Kotlin:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span class="line">&lt;span class="cl">&lt;span class="k">val&lt;/span> &lt;span class="py">zx2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">zx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">zy&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The bytecode will look roughly like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="n">aload&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="nb">load&lt;/span> &lt;span class="n">zx&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">aload&lt;/span> &lt;span class="mi">8&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="nb">load&lt;/span> &lt;span class="n">zy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">checkcast&lt;/span> &lt;span class="n">jdk&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">incubator&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">Vector&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="n">CAST&lt;/span> &lt;span class="n">zy&lt;/span> &lt;span class="n">to&lt;/span> &lt;span class="n">Vector&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">invokevirtual&lt;/span> &lt;span class="n">DoubleVector&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mul&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="n">zx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">zy&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The problem lies in &lt;code>checkcast&lt;/code>. The Vector API relies on JIT intrinsics to emit CPU vector instructions. Without intrinsification, it falls back to scalar execution. Kotlin is stricter about variance/generics than Java, and inserts a &lt;code>checkcast&lt;/code> instruction that prevents the JIT from applying those intrinsics. &lt;em>The JIT can no longer intrinsify each SIMD operation (&lt;code>sum&lt;/code>, &lt;code>mul&lt;/code>, &lt;code>blend&lt;/code>, etc)&lt;/em>.&lt;/p>
&lt;p>Compare it with the Java version:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="line">&lt;span class="cl">&lt;span class="n">DoubleVector&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">zx2&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">zx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="na">mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">zy&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And the bytecode:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="n">aload&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="nb">load&lt;/span> &lt;span class="n">zx&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">aload&lt;/span> &lt;span class="mi">8&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="nb">load&lt;/span> &lt;span class="n">zy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">invokevirtual&lt;/span> &lt;span class="n">DoubleVector&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mul&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="n">intrinsified&lt;/span> &lt;span class="n">to&lt;/span> &lt;span class="n">vmulpd&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Intrinsification&lt;/strong> means that the JIT compiler replaces the &lt;code>invokevirtual&lt;/code> by a direct CPU instruction. This is what we want for SIMD.&lt;/p>
&lt;h2 id="the-solution">The solution&lt;/h2>
&lt;p>I rewrote only the pure SIMD part of the algorithm in Java and benchmarked it again&amp;hellip; &lt;strong>4x faster than the scalar version 🤯🤯🤯&lt;/strong>.&lt;/p>
&lt;p>The performance gain will vary depending on your CPU and the JIT&amp;rsquo;s vectorization capabilities, but that was just amazing. What a ride!&lt;/p>
&lt;video controls width="100%">
&lt;source src="https://gryt.io/video/simd-kotlin-mandelbrot.webm" type="video/webm">
Your browser does not support the video tag.
&lt;/video></content></item><item><title>Enhanced vim motions in IntelliJ</title><link>https://gryt.io/til/2025-10-03-ideavim-anyobject-plugin/</link><pubDate>Fri, 03 Oct 2025 15:58:50 +0200</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/til/2025-10-03-ideavim-anyobject-plugin/</guid><description>&lt;p>vim allows powerful text manipulation: &lt;code>di&amp;quot;&lt;/code> to delete inside double quotes, &lt;code>ci(&lt;/code> to change inside brackets, etc. If you use &lt;a href="https://github.com/JetBrains/ideavim">IdeaVim&lt;/a> in JetBrains IDEs, this can be enhanced with the &lt;a href="https://plugins.jetbrains.com/plugin/28333-vim-anyobject">AnyObject Plugin&lt;/a>.&lt;/p></description><content>&lt;p>vim allows powerful text manipulation: &lt;code>di&amp;quot;&lt;/code> to delete inside double quotes, &lt;code>ci(&lt;/code> to change inside brackets, etc. If you use &lt;a href="https://github.com/JetBrains/ideavim">IdeaVim&lt;/a> in JetBrains IDEs, this can be enhanced with the &lt;a href="https://plugins.jetbrains.com/plugin/28333-vim-anyobject">AnyObject Plugin&lt;/a>.&lt;/p>
&lt;p>To enable it, just add this to your &lt;code>.ideavimrc&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">set anyobject
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After that, you can do:&lt;/p>
&lt;ul>
&lt;li>&lt;code>daa&lt;/code> - Delete argument and its separator. For example, you want to delete &lt;code>name&lt;/code> parameter in one stroke in &lt;code>def say_hi(name, email)&lt;/code>&lt;/li>
&lt;li>&lt;code>ciq&lt;/code> - Change inside &lt;em>any&lt;/em> quote. &lt;code>&amp;quot;hello&amp;quot;&lt;/code>, &lt;code>'good bye'&lt;/code>, &lt;code>`again`&lt;/code>&lt;/li>
&lt;li>&lt;code>yif&lt;/code> - Yank inner function. Copy all the function body ♥️&lt;/li>
&lt;li>&lt;code>din&lt;/code> - Delete inner block level. Especially useful for indentation based languages (Python, YAML)&lt;/li>
&lt;/ul></content></item><item><title>My Favorite (Tech) Talks</title><link>https://gryt.io/posts/favorite-tech-talks/</link><pubDate>Fri, 03 Oct 2025 13:19:48 +0200</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/posts/favorite-tech-talks/</guid><description>&lt;p>Watching (tech) talks is a hobby of mine. It puts me in a kind of meditative mood while watching the explanation unfolding.&lt;/p>
&lt;p>Here&amp;rsquo;s a list of my all-time favorite talks. Very few of them are about a specific language / framework. The majority is to make you think and re-assess your mental models.&lt;/p></description><content>&lt;p>Watching (tech) talks is a hobby of mine. It puts me in a kind of meditative mood while watching the explanation unfolding.&lt;/p>
&lt;p>Here&amp;rsquo;s a list of my all-time favorite talks. Very few of them are about a specific language / framework. The majority is to make you think and re-assess your mental models.&lt;/p>
&lt;h3 id="updated---02102025">&lt;em>Updated - 02/10/2025&lt;/em>&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=wo84LFzx5nI">The Big OOPs - Casey Muratori&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=JNCVj_RtdZw">Dynamic Phase Alignment in Audio - Sander J. Skjegstad&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=bUOOaXf9qIM">File Pilot: Inside the Engine - Vjekoslav Krajačić&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=hFMaT9oRbs4">Terence Tao continuing history’s cleverest cosmological measurements - 3Blue1Brown&lt;/a>&lt;/li>
&lt;/ul>
&lt;h3 id="updated---01032021">&lt;em>Updated - 01/03/2021&lt;/em>&lt;/h3>
&lt;p>Here are the new talks (Description in the following tables):&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=ShEez0JkOFw">Programming Clojure With Hand Tools - Tim Ewald&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://youtu.be/6avJHaC3C2U">Art of code - Dylan Beattie&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.youtube.com/watch?v=NGFhc8R_uO4">Indistinguishable From Magic - Todd Fernandez&lt;/a>&lt;/li>
&lt;/ul>
&lt;div style="text-align: center; margin: 2em 0; clear: both;">
&lt;span style="font-size: 1.5em; color: #666;">• • •&lt;/span>
&lt;/div>
&lt;h2 id="general">General&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Title&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;a href="https://www.destroyallsoftware.com/talks/boundaries">Boundaries - Gary Bernhardt&lt;/a>&lt;/td>
&lt;td>Thoughts on architecture of a project and how to define the boundaries between modules&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=_ahvzDzKdB0">Growing a Language - Guy Steel&lt;/a>&lt;/td>
&lt;td>Deep thinking about the emergence of a programming language and the paradigm that stems from it&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=f84n5oFoZBc">Hammock Driven Development - Rich Hickey&lt;/a>&lt;/td>
&lt;td>How to tackle complex problems&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=aAb7hSCtvGw">How To Design a Good API - Joshua Block&lt;/a>&lt;/td>
&lt;td>A gold mine of advices to build a solid architecture&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=PUv66718DII">Inventing On Principle - Bret Victor&lt;/a>&lt;/td>
&lt;td>A complete different take on how programming is a tool to create&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=8_UoDmJi7U8">Livable Code - Sarah Mei&lt;/a>&lt;/td>
&lt;td>What does software archicture mean today? What is going with this building metaphore?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=oytL881p-nQ">Simple Made Easy - Rich Hickey&lt;/a>&lt;/td>
&lt;td>The difference between simple and easy. Simple can become complexe, easy becomes complicated&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=wo84LFzx5nI">The Big OOPs - Casey Muratori&lt;/a>&lt;/td>
&lt;td>Where does OOP come from? How is it used and mis-used? 2h30, and every minute is worth it&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=WELBnE33dpY">What We Know We Don't Know - Hillel Wayne&lt;/a>&lt;/td>
&lt;td>We pride ourselves with our &amp;ldquo;scientific thinking&amp;rdquo;. We are however deeply driven by beliefs. What works? what doesn&amp;rsquo;t? What can we really prove?&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="low-level">Low Level&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Title&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=VUT386_GKI8">A talk near the future of Python - David Bazley&lt;/a>&lt;/td>
&lt;td>1h of LiveCoding to build a complete machine, compiled in WASM. Bonkers.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=WDIkqP4JbkE">CPU Caches Why it Matters - Scott Meyers&lt;/a>&lt;/td>
&lt;td>How does the CPU works and how can we leverage its power&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=8fi7uSYlOdc">Containers From Scratch - Liz Rice&lt;/a>&lt;/td>
&lt;td>Build Docker from scratch in 30mins in Go to understand what Docker is about&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=rX0ItVEVjHc">Data-Oriented Design and C++&lt;/a>&lt;/td>
&lt;td>Technical architecture to yield the maximum performances with your data structures&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=JNCVj_RtdZw">Dynamic Phase Alignment in Audio - Sander J. Skjegstad&lt;/a>&lt;/td>
&lt;td>How to compensate phase-shifting when recording 2 mics with incredible graphics&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=bUOOaXf9qIM">File Pilot: Inside the Engine - Vjekoslav Krajačić&lt;/a>&lt;/td>
&lt;td>What&amp;rsquo;s more common than a FileExplorer? Watch and see how incredibly optimized it can get&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=NGFhc8R_uO4">Indistinguishable From Magic - Todd Fernandez&lt;/a>&lt;/td>
&lt;td>Todd worked on CPU production and explains the process of building one. So much complexity&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://vimeo.com/341115830">JSON Explained - Jesse Wilson&lt;/a>&lt;/td>
&lt;td>Deep dive on how JSON parsing works and why it works that way&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="functional-programming">Functional programming&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Title&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=ShEez0JkOFw">Programming Clojure With Hand Tools - Tim Ewald&lt;/a>&lt;/td>
&lt;td>An elegant analogy of programming and woodworking. It starts slowly, but so satisfying&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=vK1DazRK_a0">Solving Problems The Clojure Way - Raffal Dittwald&lt;/a>&lt;/td>
&lt;td>3 ways to solve the same problem: procedural, object oriented, functional (written in JavaScript)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=QyJZzq0v7Z4">Why isn&amp;rsquo;t Functional Programming the Norm ? - Richard Feldman&lt;/a>&lt;/td>
&lt;td>History of programming language to understand why the functional paradigm is adopted (or not) by the industry&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="fun--crazy">Fun &amp;amp; Crazy&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Title&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;a href="https://youtu.be/6avJHaC3C2U">Art of code - Dylan Beattie&lt;/a>&lt;/td>
&lt;td>An exploration of every form of art using code. Fractals, music, Conway&amp;rsquo;s game of life, you name it&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.deconstructconf.com/2018/tom-murphy-vii-improper-hierarchy">Improper Hierarchy - Tom Murphy&lt;/a>&lt;/td>
&lt;td>This dude reverse engineers a Nintendo console in a peculiar way. Mind blowing.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.destroyallsoftware.com/talks/wat">Wat - Gary Bernhardt&lt;/a>&lt;/td>
&lt;td>Lightning talk about JavaScript quirkness&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=5R9eywArFTE">How to Hack a Painting - Tyler Hobbs&lt;/a>&lt;/td>
&lt;td>Mixing art and programming to (re)produce abstract paintings&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="non-tech-talks">Non Tech Talks&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Title&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=Pb5oIIPO62g">Creativity - John Cleese&lt;/a>&lt;/td>
&lt;td>Monty Python&amp;rsquo;s John Cleese explains how he sees and ritualizes creativity. Fun and insightful&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=so2nCu2Jkbc">Higgs Explained (CERN)&lt;/a>&lt;/td>
&lt;td>A multi-part CERN serie that explains the Higgs boson in layman terms&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=hFMaT9oRbs4">Terence Tao continuing history’s cleverest cosmological measurements - 3Blue1Brown&lt;/a>&lt;/td>
&lt;td>The famous mathematician Terence Tao explains how we measure astronomical distances&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=vtIzMaLkCaM">The Craft of Writing Effectively - Larry McEnerney&lt;/a>&lt;/td>
&lt;td>Mr McEnerney deconstructs the non-fiction writing process to be the most effective&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://www.youtube.com/watch?v=RjD1aLm4Thg">What Bodies Think About: Bioelectric Computation Outside the Nervous System&lt;/a>&lt;/td>
&lt;td>An incredible biotech talk that explores how the body might be linked to eletrical signals&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table></content></item><item><title>Free and self-hosted retro tool</title><link>https://gryt.io/til/2025-10-01-self-hosted-scrumlr-retro-tool/</link><pubDate>Wed, 01 Oct 2025 00:15:46 +0200</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/til/2025-10-01-self-hosted-scrumlr-retro-tool/</guid><description>&lt;p>At the end of each of my course, we do a retrospective to have all the opinions of my students. Until recently, I used &lt;a href="https://ludi.co/">ludi&lt;/a>, but the free tier is gone ☠️.&lt;/p></description><content>&lt;p>At the end of each of my course, we do a retrospective to have all the opinions of my students. Until recently, I used &lt;a href="https://ludi.co/">ludi&lt;/a>, but the free tier is gone ☠️.&lt;/p>
&lt;p>And one of my students discoverd &lt;a href="https://scrumlr.io/">scrumlr.io&lt;/a>, which can be used freely, or self-hosted with a &lt;a href="https://docs.scrumlr.io/self-hosting/intro/">docker container&lt;/a>, nice!&lt;/p></content></item><item><title>What Unit Tests tells about Human Nature</title><link>https://gryt.io/posts/unit-tests-human-nature/</link><pubDate>Mon, 17 Jun 2019 10:35:32 +0100</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/posts/unit-tests-human-nature/</guid><description>It&amp;rsquo;s Friday night. after an intense week of work, you&amp;rsquo;re about to take off to enjoy your week-end. Your boss storms in the office:
&amp;ldquo;You&amp;rsquo;re still here! Great, I need you to add a hot fix in our Phronabulax System. We expect a demo Monday morning very early, everything has to be ready!&amp;rdquo;
Well. That was unexpected. You know your boss, he/she wouldn&amp;rsquo;t ask if it wasn&amp;rsquo;t really urgent. You grumble a bit but still go dig into the code to do the work.</description><content>&lt;p>It&amp;rsquo;s Friday night. after an intense week of work, you&amp;rsquo;re about to take off to enjoy your week-end. Your boss storms in the office:&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;You&amp;rsquo;re still here! Great, I need you to add a hot fix in our Phronabulax System. We expect a demo Monday morning very early, everything has to be ready!&amp;rdquo;&lt;/p>
&lt;/blockquote>
&lt;p>Well. That was unexpected. You know your boss, he/she wouldn&amp;rsquo;t ask if it wasn&amp;rsquo;t really urgent. You grumble a bit but still go dig into the code to do the work.&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/unit-test-human-nature/bored.jpg"
onclick="openLightbox('\/img\/unit-test-human-nature\/bored.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >My enthusiasm and fervor to do this on a Friday night cannot be overstated. Thank you boss.&lt;/figcaption>
&lt;/figure>
&lt;p>30 minutes later, you commit your work and breathe a sigh of relief. &lt;strong>Wait! You completely forgot the tests! You didn&amp;rsquo;t add a new test covering the modification&lt;/strong>. There might be unintended side effects. What shall you do?&lt;/p>
&lt;p>&lt;em>Without a doubt, you shutdown your computer and leave. This can wait Monday, and the fix was trivial!&lt;/em>&lt;/p>
&lt;p>&lt;strong>Are you a bad developer? An impostor? Nope, I certainly hope not. I think you&amp;rsquo;re just a Human.&lt;/strong>&lt;/p>
&lt;h1 id="what-is-a-unit-test">What is a Unit Test?&lt;/h1>
&lt;p>I won&amp;rsquo;t delve into the details of Unit Testing, TDD (Test Driven Development) or any &lt;em>purely&lt;/em> technical aspect. &lt;strong>I&amp;rsquo;d like to scratch what is hidden beneath Unit Tests. More precisely, what it reveals about us, about our Human Nature.&lt;/strong>&lt;/p>
&lt;p>The formal definition of a Unit test by &lt;a href="https://en.wikipedia.org/wiki/Unit_testing">Wikipedia&lt;/a> states:&lt;/p>
&lt;blockquote>
&lt;p>In computer programming, unit testing is a software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use&lt;/p>
&lt;/blockquote>
&lt;p>As any proper formal definition, it&amp;rsquo;s absolutely opaque for anyone who doesn&amp;rsquo;t already know what is a Unit Test.&lt;/p>
&lt;p>One might ask &lt;em>&amp;ldquo;What&amp;rsquo;s the point of the definition then?&amp;rdquo;&lt;/em>. To look educated and unreachable my dear!&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/unit-test-human-nature/bridge-blueprint.jpg"
onclick="openLightbox('\/img\/unit-test-human-nature\/bridge-blueprint.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >When you ask why the bridge is red and your friend starts explaining.&lt;/figcaption>
&lt;/figure>
&lt;p>A Unit Tests is a piece of code that you write that will execute a subset of your real program to verify its behavior.&lt;/p>
&lt;p>If I input &lt;code>5&lt;/code>, it should spit &lt;code>&amp;quot;Hello&amp;quot;&lt;/code>, If I input &lt;code>42&lt;/code>, it should crash, and so on.&lt;/p>
&lt;h1 id="i-dont-have-the-time-to-write-tests">I don&amp;rsquo;t have the time to write tests&lt;/h1>
&lt;p>And I agree with you, me neither. &lt;em>Writing tests requires time, the structure of the test is a craft in itself&lt;/em>. Having a decent test coverage is a real effort. Still, I have a question for you:&lt;/p>
&lt;h3 id="do-you-test-what-you-develop">Do you test what you develop?&lt;/h3>
&lt;p>I mean it in the simplest sense: &lt;strong>you write some code, run the program and check if it works as intended. Chances are, you are doing it all the time&lt;/strong>. You might even yell at your computer because the compilation / startup time to test it takes more than 2,4 seconds.&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/unit-test-human-nature/harakiri.jpg"
onclick="openLightbox('\/img\/unit-test-human-nature\/harakiri.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Compilation took 4 seconds, my mug is empty and the Coffee Machine is broken. This is unbearable.&lt;/figcaption>
&lt;/figure>
&lt;p>&lt;strong>Unit Tests are merely an automated way of doing what you already do by hand&lt;/strong>. But I understand, it&amp;rsquo;s time consuming and the feature has to be delivered yesterday. So why write tests?&lt;/p>
&lt;h2 id="human-after-all">Human After All&lt;/h2>
&lt;p>&lt;em>Because you are just a Human! I believe testing reveals something very interesting about us, as Humans: we&amp;rsquo;re absolutely fallible, and even more while coding.&lt;/em>&lt;/p>
&lt;p>Don&amp;rsquo;t believe me?&lt;/p>
&lt;p>What is the quality of you work when you slept 3 hours ? When there&amp;rsquo;s a disturbing noise in the office? When you neighbor keeps chewing that dreadful, juicy candy?&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/unit-test-human-nature/terminator.jpg"
onclick="openLightbox('\/img\/unit-test-human-nature\/terminator.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >My sleep routine is tight, my work is perfect. Any question?&lt;/figcaption>
&lt;/figure>
&lt;p>Let&amp;rsquo;s assume you always sleep 8 hours each night and that your noise canceling headphones does wonder.&lt;/p>
&lt;p>&lt;strong>What about &lt;em>that&lt;/em> dark corner of the project?&lt;/strong> The one that makes everyone look at their feet when you need to modify it? It was written ages ago by a dark ninja-shaman on a moonless night? Nobody understands it and even less dares to touch it?&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/unit-test-human-nature/project-octopus.jpg"
onclick="openLightbox('\/img\/unit-test-human-nature\/project-octopus.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >I don't want to make a fix below water level. PLEASE GOD NO!&lt;/figcaption>
&lt;/figure>
&lt;p>Yes, it makes you cringe every time you approach it.&lt;/p>
&lt;p>That&amp;rsquo;s exactly why Unit Tests exist: because you&amp;rsquo;re just a Human. You can be tired, angry, not concentrated. &lt;strong>Because you don&amp;rsquo;t have the time nor the energy, the Unit Test will execute the 526 cases that you don&amp;rsquo;t want to check yet another time.&lt;/strong>&lt;/p>
&lt;h2 id="down-the-rabbit-hole">Down the Rabbit Hole&lt;/h2>
&lt;p>What I find incredible is not that the discovery that we are deeply flawed. The Atlantic Ocean could be filled with the ink spilled on this topic for the last 3000 years.&lt;/p>
&lt;p>&lt;strong>What is amazing is our resourcefulness&lt;/strong>. As soon as we admit that eating every dish of that damn Chinese self-serve buffet turns us into brainless oysters for the rest of the afternoon, we start constructing countermeasures.&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/unit-test-human-nature/oyster.jpg"
onclick="openLightbox('\/img\/unit-test-human-nature\/oyster.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >I shall open at the afternoon break. Until then, let me die in my soy sauce.&lt;/figcaption>
&lt;/figure>
&lt;p>Unit Test are for me the product of humble yet very sharp minds, who acknowledge their weakness but still want to get the job done, in the best possible way.&lt;/p>
&lt;p>&lt;strong>See it that way: we are tool builders. We don&amp;rsquo;t have claws or big furs, so we build tools to compensate. Writing tests is an answer to our daily stupidity.&lt;/strong>&lt;/p>
&lt;h2 id="the-beatitude-of-the-fool">The Beatitude Of The Fool&lt;/h2>
&lt;p>When I started doing tests, I was closer to the attitude of the rebel Teenager than the excited newbie: &lt;em>&amp;ldquo;Yeaaaah! Whatever. I still ship good code. Meh&amp;rdquo;&lt;/em>.&lt;/p>
&lt;p>But, as the test coverage grew, I started to notice an interesting pattern:&lt;/p>
&lt;ol>
&lt;li>Customer requests feature X&lt;/li>
&lt;li>I implement feature X&lt;/li>
&lt;li>I don&amp;rsquo;t write any test, feeling confident. The day was sunny, my focus was Zen-like while writing the code&lt;/li>
&lt;li>I ship the feature X&lt;/li>
&lt;li>Customer gets back to me &lt;em>&amp;ldquo;Thanks Robin! X works great, however Y is broken!&amp;rdquo;&lt;/em>&lt;/li>
&lt;li>&lt;em>I&amp;rsquo;m sorry what?&lt;/em> I dig in, and see the regression&lt;/li>
&lt;li>I (finally) write a test, the test fails. I fix the bug and ship again&lt;/li>
&lt;/ol>
&lt;p>Rince and repeat. That feeling, between the &lt;em>&amp;ldquo;I&amp;rsquo;m sorry what?&amp;rdquo;&lt;/em> and the understanding that by simply adding a couple of tests, the bug could have been prevented is priceless. It&amp;rsquo;s a mix of shame and anger.&lt;/p>
&lt;p>After a couple of iterations, I &lt;em>systematically&lt;/em> wrote a test for any feature. And that my friends, was a pure Dopamine shot.&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/unit-test-human-nature/parachute-hole.jpg"
onclick="openLightbox('\/img\/unit-test-human-nature\/parachute-hole.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >We have a great Tests Coverage. The project is bulletproof!&lt;/figcaption>
&lt;/figure>
&lt;p>&lt;strong>Now, I see my tests as a handcrafted parachute. I can code and feel the bliss of the Fool. I can be Human, stupid, tired, and my tests have my back. Oh, what a feeling!&lt;/strong>&lt;/p>
&lt;h2 id="side-effects-and-cult">Side Effects and Cult&lt;/h2>
&lt;p>A beautiful side-effect of testing your program, &lt;em>is that it puts you in the shoes of the user of your API&lt;/em>. You write an incredibly efficient program, great! How usable it is?&lt;/p>
&lt;p>Remember, you don&amp;rsquo;t have the time to write tests, &lt;strong>do you think your colleague will have the time to understand your crazy Logorrhea of functions, variables and convoluted classes? Of course not!&lt;/strong>&lt;/p>
&lt;p>Testing gives you a feedback on how your API &lt;em>feels&lt;/em>. This is extremely useful, because you won&amp;rsquo;t be able to know that before having to modify it again.&lt;/p>
&lt;p>However, I want to add a big nuance:&lt;/p>
&lt;h3 id="not-everything-should-be-tested">Not everything should be tested&lt;/h3>
&lt;p>You know how it&amp;rsquo;s called when someone tells you &lt;em>&amp;ldquo;If you don&amp;rsquo;t test X, you&amp;rsquo;re worthless&amp;rdquo;&lt;/em>? &lt;strong>Religion Fever&lt;/strong>.&lt;/p>
&lt;p>&lt;em>In my humble opinion, it&amp;rsquo;s as stupid to refuse to test anything than to insist on testing absolutely everything.&lt;/em>&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/unit-test-human-nature/prayer.jpg"
onclick="openLightbox('\/img\/unit-test-human-nature\/prayer.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Our brother Robin did not write a test for the project POC-to-be-dropped-and-rewritten-anyway. He shall be excommunicated from the Church Of The Right Software.&lt;/figcaption>
&lt;/figure>
&lt;p>Some situations makes the tests impossibly hard to write or maintain.&lt;/p>
&lt;p>In many cases, I don&amp;rsquo;t write any UI test. Why? Because I have a limited time, and I honestly care little about UI testing. It makes perfect sense for huge projects, with very strict UI designs.&lt;/p>
&lt;p>&lt;strong>I see testing more as a state of mind, a humbling affirmation of our imperfection and a celebration of our imagination to face it.&lt;/strong>&lt;/p>
&lt;p>&lt;em>As with any craft, it has to be practiced and repeated&lt;/em>. Sometimes your tests will be useless and badly written. Other times it will fit perfectly to your project.&lt;/p>
&lt;p>&lt;strong>It takes time, but it&amp;rsquo;s worthwhile. You&amp;rsquo;ll thank yourself for doing it, trust me.&lt;/strong>&lt;/p></content></item><item><title>Powerpoint and Chickens</title><link>https://gryt.io/posts/powerpoint-and-chickens/</link><pubDate>Thu, 20 Dec 2018 15:32:12 +0100</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/posts/powerpoint-and-chickens/</guid><description>Do you remember that day — probably a rainy one — when you sat in a conference room? Your heart was filled with eagerness to dive into the speaker&amp;rsquo;s mind. You even prepared by documenting about the topic, to fully comprehend the genius striding in front of you.
The common purpose And then, as the drama unfolded in the room and the rain kept pouring outside, this slide came up:</description><content>&lt;p>&lt;strong>Do you remember that day — probably a rainy one — when you sat in a conference room? Your heart was filled with eagerness to dive into the speaker&amp;rsquo;s mind. You even prepared by documenting about the topic, to fully comprehend the genius striding in front of you.&lt;/strong>&lt;/p>
&lt;h2 id="the-common-purpose">The common purpose&lt;/h2>
&lt;p>And then, as the drama unfolded in the room and the rain kept pouring outside, this slide came up:&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/powerpoint-chicken/powerpoint-presentation.jpg"
onclick="openLightbox('\/img\/powerpoint-chicken\/powerpoint-presentation.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Here's a short summary of my ideas, in 5 small bullet points.&lt;/figcaption>
&lt;/figure>
&lt;p>&lt;em>&amp;ldquo;Oh dear&amp;rdquo;&lt;/em>, you whispered, &lt;em>&amp;ldquo;Not again&amp;hellip; Not this Powerpoint presentation&amp;hellip;&amp;rdquo;&lt;/em>. You know it when you see it. The Powerpoint to end all Powerpoints. The one with slides so dense, that a pair of opera-binoculars should be provided on each chair.&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/powerpoint-chicken/opera-binoculars.jpg"
onclick="openLightbox('\/img\/powerpoint-chicken\/opera-binoculars.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >The mandatory tool to brain numbing presentations.&lt;/figcaption>
&lt;/figure>
&lt;p>Yes my friend, you&amp;rsquo;re about to spend a long, excruciating hour. You&amp;rsquo;ll watch the speaker spit 3 zillion data per minute, while your brain turns into a mishmash of tears, agony, and forgotten eastern curses inherited from your mother side. Who would have guessed that these memories could be retrieved? Bonkers!&lt;/p>
&lt;p>I call this phenomenon the &lt;strong>Chicken Hypnosis&lt;/strong>, or the &lt;em>Gallus Gallus Apathy&lt;/em> — Latin makes things so &lt;em>chic&lt;/em> —. We have all seen dozens of Powerpoint presentations where you ask yourself: &lt;strong>What is the point of this Powerpoint? To help the speaker or to hypnotize chickens, namely, the audience?&lt;/strong>&lt;/p>
&lt;h2 id="the-real-purpose">The &lt;em>real&lt;/em> purpose&lt;/h2>
&lt;p>Much ink has been spilled on the Powerpoint issue: Back in 2003, the NASA pointed that &lt;a href="https://www.nytimes.com/2003/12/14/magazine/2003-the-3rd-annual-year-in-ideas-powerpoint-makes-you-dumb.html">Powerpoint makes you dumb&lt;/a>, some physicists chose to &lt;a href="https://www.symmetrymagazine.org/article/march-2014/physics-by-hand">ban Powerpoint altogether&lt;/a> and searching for &amp;ldquo;Powerpoint hate&amp;rdquo; on Google yields &lt;a href="https://www.google.com/search?q=powerpoint+hate">24 400 000 results&lt;/a>.&lt;/p>
&lt;p>But still, (almost) everybody uses a form of Powerpoint to do a presentation. It can&amp;rsquo;t be for nothing! &lt;strong>Indeed, I believe that a Powerpoint has a great purpose: to &lt;em>support&lt;/em> the speaker. To provide some anchor, in the turmoil of a long explanation.&lt;/strong>&lt;/p>
&lt;p>A Powerpoint purpose is 2-folded:&lt;/p>
&lt;ul>
&lt;li>&lt;em>&lt;strong>Condense information&lt;/strong>&lt;/em> in a digestible format to sum important points&lt;/li>
&lt;li>&lt;em>&lt;strong>Provide multimedia content&lt;/strong>&lt;/em> to animate the presentation&lt;/li>
&lt;/ul>
&lt;p>But, what &lt;strong>really&lt;/strong> matters is the explanation of the speaker. Filling slides up to death is an innocent act of laziness, not malevolence.&lt;/p>
&lt;h2 id="how-did-we-get-there">How did we get there?&lt;/h2>
&lt;h3 id="tool--user">Tool &amp;amp; User&lt;/h3>
&lt;p>I think Powerpoint is a very efficient tool. Without any prior knowledge, you can put together titles, bullet points and incredible-yet-utterly-useless animations.&lt;/p>
&lt;p>&lt;strong>However, who is using Powerpoint? For the vast majority, people who are not professional speakers (me too!)&lt;/strong>. They are business men, developers, physicists, or anything that is not directly centered on a Speaker / User Experience. For them, &lt;em>making a presentation is mandated by their job, but they surely didn&amp;rsquo;t study to do presentations or animate conferences.&lt;/em>&lt;/p>
&lt;p>Mix the ease of use of Powerpoint with the lack of interest of its users, and you get this new Super-Villain: &lt;strong>The Destroyer Of Minds (DOM for the closest friends)&lt;/strong>.&lt;/p>
&lt;h3 id="misuse-of-the-tool">Misuse of the Tool&lt;/h3>
&lt;p>From here, the resulting presentation has many chances of being failed. We already covered the densely-packed slides, but there&amp;rsquo;s another killer hiding in plain sight: &lt;strong>The lifeless presentation&lt;/strong>.&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/powerpoint-chicken/deck-alone.jpg"
onclick="openLightbox('\/img\/powerpoint-chicken\/deck-alone.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Will he finish the talk? Will he jump? Will the restaurant still have my favorite Cheesecake?&lt;/figcaption>
&lt;/figure>
&lt;p>Everything &lt;em>feels&lt;/em> empty. There can be a lot on each slide&amp;hellip; Or not. The speaker is slow and monotonous. Add a last slide, and the abyss will swallow you in the eternal bliss of infinite boredom.&lt;/p>
&lt;p>In this case, the &lt;em>Powerpoint has taken the driver seat&lt;/em>. The trigger to speak is the &lt;em>&amp;ldquo;Next Slide Button&amp;rdquo;&lt;/em> — not what the speaker intends to say.&lt;/p>
&lt;h3 id="down-the-rabbit-hole">Down the Rabbit Hole&lt;/h3>
&lt;p>It gets even worse. To be so easy to use, Powerpoint has a killer-feature: &lt;strong>The Bullet Point&lt;/strong>.&lt;/p>
&lt;p>It might seem innocuous, but I think it&amp;rsquo;s exactly the reason for which presentations get such a bad press: &lt;strong>it transforms complex ideas into a sequence of bullet points, waiting to be unstacked, one by one&lt;/strong>.&lt;/p>
&lt;p>A presentation can have multiple purposes: make you think, spark a heated debate, provide a different perspective on a known issue.&lt;/p>
&lt;p>A set of bullet points is a drowning of creativity. &lt;strong>Said plainly, you cannot express complex ideas with bullet points&lt;/strong>. &lt;em>It&amp;rsquo;s like describing a car with karate moves: it&amp;rsquo;s not meant for that&lt;/em>.&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/powerpoint-chicken/karate.jpg"
onclick="openLightbox('\/img\/powerpoint-chicken\/karate.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >"Here are the seats, and you can lower the windows like so." — Damn, this picture could almost make the opposite point! Note to self: choose a worse karate picture.&lt;/figcaption>
&lt;/figure>
&lt;p>Still, why do we cram so much data in our slides? &lt;em>Because we have the illusion that the audience must know everything&lt;/em>. &lt;strong>WRONG&lt;/strong>! 90% of the time, you are presenting your deep work to people who might not have the slightest idea of your passion.&lt;/p>
&lt;p>They are just curious / forced to be here / bored / sitting in the wrong conference room.&lt;/p>
&lt;blockquote>
&lt;p>Oh dear, this is about the Java programming language? I&amp;rsquo;m just a Peruvian Coffee Beans aficionado!&lt;/p>
&lt;/blockquote>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/powerpoint-chicken/coffee-bag.jpg"
onclick="openLightbox('\/img\/powerpoint-chicken\/coffee-bag.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >As you can see, the Abstract Factory Pattern makes it so easy to spawn new objects.&lt;/figcaption>
&lt;/figure>
&lt;h2 id="how-should-we-approach-it">How should we approach it?&lt;/h2>
&lt;p>You might yell at me:&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>&amp;ldquo;Very well then, should I empty my slides? Fill them? Simplify everything? Put only complex diagrams?&amp;rdquo;&lt;/strong>&lt;/p>
&lt;/blockquote>
&lt;p>Breath. I understand. You think about &lt;em>that&lt;/em> presentation you already hated even before finishing it. We all have been culprit of such an abomination.&lt;/p>
&lt;p>I will calmly answer with 2 questions: &lt;strong>Who is your audience? What are your constraints?&lt;/strong>&lt;/p>
&lt;p>&lt;em>If you can&amp;rsquo;t answer, the presentation is already doomed.&lt;/em> Call it a day and start doing some improv class, you&amp;rsquo;ll have better chances.&lt;/p>
&lt;p>&lt;em>Is your audience young children, discovering science? International astrophysists waiting for your Boson revelation? Do you have 20 mins or 2 hours? Can you draw something while talking? Is it online or in a real room, with real people?&lt;/em>&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/powerpoint-chicken/speaker.jpg"
onclick="openLightbox('\/img\/powerpoint-chicken\/speaker.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >They told me that it will be in Sydney's Opera with Kung-Fu Senseis. Something's Wrong.&lt;/figcaption>
&lt;/figure>
&lt;p>&lt;strong>This is going to determine the shape and content of your presentation&lt;/strong>. Who cares if you used a distributed micro-service architecture if your audience can&amp;rsquo;t tell the difference between Internet and Google Chrome?&lt;/p>
&lt;p>If the answer to these questions keeps Powerpoint in the race, I can see 2 possible presentation general &amp;ldquo;shapes&amp;rdquo;.&lt;/p>
&lt;h3 id="tbp-aka-the-big-presentation">TBP aka The Big Presentation&lt;/h3>
&lt;p>You give an important talk, on a topic you have been working on for months / years. &lt;strong>By God, reduce the load per slice, add schemas and animations to make it easier to grasp the idea&lt;/strong>. Yes, use bullets point to sum up and the end of a section, but not to articulate a complex topic.&lt;/p>
&lt;p>Your hard work deserves a proper presentation. It would be a shame to flush your years-long sweat in the toilet of &amp;ldquo;Slides Mediocrity&amp;rdquo;. Your audience &lt;em>will probably not&lt;/em> have the same depth as you do. And if they do, the questions is the perfect time to dig in.&lt;/p>
&lt;p>If you already know you&amp;rsquo;re dealing with specialists, add references, data, but remember: &lt;strong>YOU&lt;/strong> are the reason they&amp;rsquo;re wasting their life in this small room for an hour. Not your slides. Not your fancy animations.&lt;/p>
&lt;h3 id="machine-gun-slides">Machine-gun Slides&lt;/h3>
&lt;p>If you have (like myself in recent instances) have to produce hundreds of slides on a topic, you have to be efficient rather than beautiful.&lt;/p>
&lt;p>Getting your point across without spending 2 hours on each Powerpoint can make a difference. &lt;strong>Again, know your constraints and deal with it&lt;/strong>.&lt;/p>
&lt;p>Some people have such a mastery of the content that the Powerpoint becomes a character of the presentation. For example, Larry Lessig, with his &lt;em>&amp;ldquo;Lessig Method&amp;rdquo;&lt;/em> is known to &lt;a href="https://www.ted.com/talks/larry_lessig_says_the_law_is_strangling_creativity">fire many, many slides&lt;/a> for a single presentation. And it works!&lt;/p>
&lt;h2 id="a-tool--nothing-more-nothing-less-">A tool — nothing more, nothing less —&lt;/h2>
&lt;p>Everyone has a love / hate relationship with Powerpoint. In the end, it&amp;rsquo;s just a tool. Like any tool, it can be properly used and misused.&lt;/p>
&lt;p>Because it&amp;rsquo;s easy to start with, you can dump your Ph.D thesis in your slides. Job done, back to &lt;em>real&lt;/em> work. Not so fast!&lt;/p>
&lt;p>Your presentation &lt;strong>is a part of your work&lt;/strong>. Don&amp;rsquo;t spoil it by doing a cheap stunt. You deserve better.&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/powerpoint-chicken/powerpoint-presentation-chicken.jpg"
onclick="openLightbox('\/img\/powerpoint-chicken\/powerpoint-presentation-chicken.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >The Gallus Gallus Apathy can, and should be prevented! To arms my fellow Powerpointers!&lt;/figcaption>
&lt;/figure>
&lt;p>Powerpoint is &lt;em>a&lt;/em> tool, not &lt;em>the&lt;/em> tool. &lt;strong>When you only have a hammer, every problem looks like a nail.&lt;/strong>&lt;/p>
&lt;p>Surrendering is transforming yourself into a nail, while Powerpoint is happily smashing on you.&lt;/p>
&lt;p>Surrendering, is transforming your audience into chickens.&lt;/p>
&lt;p>&lt;strong>You&amp;rsquo;re not a Nail nor a Chicken. You&amp;rsquo;re a Human. Embrace it.&lt;/strong>&lt;/p></content></item><item><title>Qt Model / View</title><link>https://gryt.io/posts/understanding-qt-modelview-read/</link><pubDate>Thu, 25 Oct 2018 15:00:12 +0200</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/posts/understanding-qt-modelview-read/</guid><description>The Shock The first time I&amp;rsquo;ve read the Qt Model / View documentation, I was shocked. Flabbergasted. Blown away. I started with a vague idea of the purpose of the Model / View, and finished convinced that I understood even less. My first implementation of the pattern was an utter failure: it looked like a Frankenstein implementation of a boilerplate code written by a crazy monkey. This was not right, the Qt documentation is known to be of quality — detailed, explanatory and coherent.</description><content>&lt;h2 id="the-shock">The Shock&lt;/h2>
&lt;p>&lt;strong>The first time I&amp;rsquo;ve read the &lt;a href="http://doc.qt.io/qt-5/model-view-programming.html">Qt Model / View documentation&lt;/a>, I was shocked. Flabbergasted. Blown away. I started with a vague idea of the purpose of the Model / View, and finished convinced that I understood even less. My first implementation of the pattern was an utter failure: it looked like a Frankenstein implementation of a boilerplate code written by a crazy monkey. This was not right, the Qt documentation is known to be of quality — detailed, explanatory and coherent&lt;/strong>.&lt;/p>
&lt;p>After multiple attempts and many hours reading and re-reading the doc, I started to grasp the concept. The deep understanding came when I had to write about this topic. Heck, I&amp;rsquo;ve written &lt;a href="https://www.amazon.com/Mastering-Qt-stunning-cross-platform-applications/dp/1786467127/">whole chapters&lt;/a> on the Model / View!&lt;/p>
&lt;p>I entertain myself imagining how this marathon-length documentation came to be:&lt;/p>
&lt;blockquote>
&lt;p>A Qt framework developer glances over the Model / View documentation and utters to himself &lt;strong>&amp;ldquo;This is not right. It makes no sense at all&amp;rdquo;&lt;/strong>. He then pounds on his keyboard in a sweated rush of the documentation-muse inspiration. He looks at his masterpiece and proudly says &lt;strong>&amp;ldquo;It&amp;rsquo;s now much clearer! Let&amp;rsquo;s add my stone to the cathedral and update the official doc&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;/blockquote>
&lt;p>After several iterations of this episode, the current doc now takes longer to read than The Lord Of the Rings. And boy, it&amp;rsquo;s a long story! Don&amp;rsquo;t get me wrong, the documentation is exhaustive in the deepest sense of exhaustiveness. But, it&amp;rsquo;s also exhausting when you discover the Model / View pattern. The underlying principle is drown in a plethora of details which are useless at the beginning.&lt;/p>
&lt;p>This article aims for exactly that, &lt;strong>understanding the Model / View pattern with a simple example&lt;/strong>. I won&amp;rsquo;t cover all the bells and whistles that you can implement with Model / View.&lt;/p>
&lt;h2 id="tldr--show-me-the-code">TL;DR — Show me the code!&lt;/h2>
&lt;p>You just want the source code to understand all by yourself? Everything is available on &lt;a href="https://github.com/synapticvoid/sample-qt-model-view">GitHub&lt;/a>. Don&amp;rsquo;t worry, it&amp;rsquo;s less than 150 lines of C++.&lt;/p>
&lt;h2 id="the-goal">The Goal&lt;/h2>
&lt;p>The goal is simple: build a simple Model / View example of a list of countries. It will be a read-only Model, no editing to keep things really simple (the edition will be covered in a separate post). We&amp;rsquo;ll display a list of countries and upon a country selection, a country details widget will be updated.&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/qt-modelview-read/model-view-read-countries.png"
onclick="openLightbox('\/img\/qt-modelview-read\/model-view-read-countries.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >The final Model / View application&lt;/figcaption>
&lt;/figure>
&lt;p>A country is composed of 3 pieces of data:&lt;/p>
&lt;ul>
&lt;li>Name&lt;/li>
&lt;li>Flag&lt;/li>
&lt;li>Capital&lt;/li>
&lt;/ul>
&lt;h2 id="the-principle">The Principle&lt;/h2>
&lt;p>In my mind, the Model / View has 2 main goals:&lt;/p>
&lt;ol>
&lt;li>Cleanly separate the model&amp;rsquo;s data source from the display. This makes the update of the data source less prone to impact the View side.&lt;/li>
&lt;li>Leverage the Qt signal / slot mechanism to update multiple views from a unified data source.&lt;/li>
&lt;/ol>
&lt;p>Here&amp;rsquo;s a simplified diagram of the Model / View architecture:&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/qt-modelview-read/model-view.svg"
onclick="openLightbox('\/img\/qt-modelview-read\/model-view.svg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Model / View architecture diagram&lt;/figcaption>
&lt;/figure>
&lt;ul>
&lt;li>&lt;strong>You feed the Model with your data source&lt;/strong>. It can be a CSV file, rows from a database query or the result of an HTTP request. The &lt;em>Model&lt;/em> name is misleading, it actually handles your &lt;em>model classes&lt;/em>. In our example, we&amp;rsquo;ll create a &lt;code>CountryModel&lt;/code> that will be the owner of a &lt;code>Country&lt;/code> list. The real data is in the &lt;code>Country&lt;/code> class, the &lt;code>CountryModel&lt;/code> just loads it and makes it available for potential clients.&lt;/li>
&lt;li>&lt;strong>The data is provided to the View through the Model&lt;/strong>. The View can be a Qt type : &lt;code>QListView&lt;/code>, or &lt;code>QTableView&lt;/code>. It really depends on what your model can offer (rows and/or columns) and what type of display your want. To enforce the separation, the format is completely domain agnostic. Here, there&amp;rsquo;s no &lt;code>Country&lt;/code> class at all, only a generic bundle. We&amp;rsquo;ll come back soon to this.&lt;/li>
&lt;li>&lt;strong>The user interacts with the View to produce events&lt;/strong> — select a row, delete a row, etc. Each event triggers an exchange between the View and the Model. The user wants to delete a row? Tell the Model about it and wait for the result with a slot.&lt;/li>
&lt;/ul>
&lt;p>The beauty of this architecture is that it fulfills our 2 goals:&lt;/p>
&lt;ol>
&lt;li>The data source can be completely changed from CSV values to Database queries, the View would not have any idea about it.&lt;/li>
&lt;li>Because the Model provides signals and slots, we can even connect multiple Views on a single Model. Each View will connect to the Model&amp;rsquo; slots and get any update.&lt;/li>
&lt;/ol>
&lt;h2 id="the-model">The Model&lt;/h2>
&lt;p>The code is done inside a Qt Widget project. We&amp;rsquo;ll start the basic build block — the &lt;code>Country.h&lt;/code> class:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;QString&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">struct&lt;/span> &lt;span class="nc">Country&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QString&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QString&lt;/span> &lt;span class="n">capital&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QString&lt;/span> &lt;span class="n">flagIcon&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Country&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">QString&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">QString&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">capital&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">QString&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">flagIcon&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">capital&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">flagIcon&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">flagIcon&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The &lt;code>flagIcon&lt;/code>is the path to the PNG icon stored in a &lt;code>.qrc&lt;/code> file, nothing fancy. The real deal starts with &lt;code>CountryModel.h&lt;/code>, the owner of a &lt;code>Country&lt;/code> list:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CountryModel&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="k">public&lt;/span> &lt;span class="n">QAbstractListModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Q_OBJECT&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">enum&lt;/span> &lt;span class="nc">CountryRoles&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CapitalRole&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Qt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">UserRole&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CountryModel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">QObject&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">parent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">~&lt;/span>&lt;span class="n">CountryModel&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="nf">rowCount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">QModelIndex&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">parent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">QModelIndex&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="k">override&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QVariant&lt;/span> &lt;span class="nf">data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">QModelIndex&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">index&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">role&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="k">override&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QVector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Country&lt;/span>&lt;span class="o">*&amp;gt;&lt;/span> &lt;span class="n">countries&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Let&amp;rsquo;s break it down. &lt;code>CountryModel&lt;/code>extends &lt;a href="http://doc.qt.io/qt-5/qabstractlistmodel.html">QAbstractListModel&lt;/a>, which is a kind of Model that provides rows of data. The View displaying &lt;code>CountryModel&lt;/code>&amp;rsquo;s data will configure itself by calling:&lt;/p>
&lt;ul>
&lt;li>&lt;code>rowCount()&lt;/code> to measure its widget size according to the number of rows.&lt;/li>
&lt;li>&lt;code>data()&lt;/code> to retrieve the actual data. It returns a &lt;a href="http://doc.qt.io/qt-5/qvariant.html">QVariant&lt;/a>, the generic Qt union type to transfer data back and forth without being restricted by a strong C++ type.&lt;/li>
&lt;/ul>
&lt;p>These functions come from &lt;code>QAbstractListModel&lt;/code>, so we have to override them to provide the real data size and content. For our purpose, the data is simply a &lt;code>QVector&lt;/code> of &lt;code>Country*&lt;/code>.&lt;/p>
&lt;p>Now, &lt;code>rowCount()&lt;/code> and &lt;code>data()&lt;/code> both take a special argument: a &lt;code>QModelIndex&lt;/code>. We&amp;rsquo;ll dive in greater length about this topic in the post about the editable Model / View. For now, just consider it as an opaque object that handles data indexes from the Model&amp;rsquo;s perspective. To retrieve a &lt;code>Country&lt;/code> from &lt;code>countries&lt;/code> at a specific index, we&amp;rsquo;ll do an &lt;code>index.row()&lt;/code>. Things get hairy when we update the data — adding, deleting lines — because this will mess up the index. That&amp;rsquo;s why Qt uses an internal index handler to keep track of what moved with &lt;code>QModelIndex&lt;/code>.&lt;/p>
&lt;p>Finally, the &lt;code>enum CountryRoles&lt;/code>. We&amp;rsquo;ll skip it for now. It will come to light in the implementation of the function &lt;code>data()&lt;/code>.&lt;/p>
&lt;div style="text-align: center; margin: 2em 0; clear: both;">
&lt;span style="font-size: 1.5em; color: #666;">• • •&lt;/span>
&lt;/div>
&lt;p>Let&amp;rsquo;s switch the &lt;code>CountryModel.cpp&lt;/code>, with &lt;code>CountryModel&lt;/code>&amp;rsquo;s contructor &amp;amp; destructor:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;QIcon&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">CountryModel&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">CountryModel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">QObject&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">parent&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QAbstractListModel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">countries&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">countries&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Country&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;France&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Paris&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;:/flags/fr.png&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Country&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Great Britain&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;London&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;:/flags/gb.png&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Country&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Japan&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Tokyo&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;:/flags/jp.png&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Country&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;United States of America&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Washington, D.C&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;:/flags/us.png&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">CountryModel&lt;/span>&lt;span class="o">::~&lt;/span>&lt;span class="n">CountryModel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qDeleteAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">countries&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>countries&lt;/code> is initialized in the constructor. You can replace it with whatever your data source is: database, CSV file, etc. The country&amp;rsquo;s icon is a path pointing to a &lt;code>.qrc&lt;/code> resource stored in the project. The destructor just cleans up the &lt;code>Country*&lt;/code>stored in countries.&lt;/p>
&lt;p>The meat of the class lies in the following functions, starting with &lt;code>rowCount()&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="n">CountryModel&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">rowCount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">QModelIndex&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">parent&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Q_UNUSED&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">countries&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The function takes a &lt;code>QModelIndex&amp;amp; parent&lt;/code>, because you data source could be tree (think of a directory / files structure). Each directory would be a &lt;code>parent&lt;/code>, and its size would be different from its neighbors. As our model is a flat list, we can safely ignore the &lt;code>parent&lt;/code> and return &lt;code>countries.size()&lt;/code>.&lt;/p>
&lt;p>Next, the implementation of &lt;code>data()&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">QVariant&lt;/span> &lt;span class="n">CountryModel&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">QModelIndex&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">index&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">role&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">const&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">const&lt;/span> &lt;span class="n">Country&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">country&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">countries&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">at&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">row&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">switch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">role&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="n">Qt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="nl">DisplayRole&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">country&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="n">Qt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="nl">DecorationRole&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">QIcon&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">country&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">flagIcon&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="n">CountryRoles&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="nl">CapitalRole&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">country&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">capital&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">default&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">QVariant&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So, &lt;code>data()&lt;/code> takes 2 parameters:&lt;/p>
&lt;ul>
&lt;li>&lt;code>index&lt;/code> to know the index of the requested data — the country index in our case.&lt;/li>
&lt;li>&lt;code>role&lt;/code> to indicate the type of the data it wants to retrieve.&lt;/li>
&lt;/ul>
&lt;p>&lt;code>data()&lt;/code> &lt;em>is not called to retrieve a whole row&lt;/em> (i.e. a full &lt;code>Country&lt;/code> object). It&amp;rsquo;s &lt;strong>&lt;em>called for each piece of data it wants to display&lt;/em>&lt;/strong>. How does it know which piece of data it wants to display? That&amp;rsquo;s the point of the &lt;code>role&lt;/code> parameter! There are &lt;a href="http://doc.qt.io/qt-5/qt.html#ItemDataRole-enum">predefined roles&lt;/a> that can be requested by the View. In a &lt;code>QListView&lt;/code>, the content of a displayed row will be &lt;code>Qt::DisplayRole&lt;/code>.&lt;/p>
&lt;p>First, we retrieve the &lt;code>Country&lt;/code> for a given &lt;code>index.row()&lt;/code>. Then, the &lt;code>switch(role) { … }&lt;/code> block comes into play. if the View want&amp;rsquo;s to see the &lt;code>Qt::DisplayRole&lt;/code>, we return the &lt;code>country.name&lt;/code>. If the View wants to see the &lt;code>Qt::DecorationRole&lt;/code> (the icon just at the left of the row label), we return a &lt;code>QIcon(contry.flagIcon)&lt;/code>. Every return value must be able to be converted to a &lt;code>QVariant&lt;/code>, this lets &lt;code>data()&lt;/code> to have a generic signature.&lt;/p>
&lt;p>Alright, the principle seems sound. But what if we want to display custom data? Here, a &lt;code>Country&lt;/code> also owns a &lt;code>capital&lt;/code>, which could — and will — be used by the View. That&amp;rsquo;s where the forgotten &lt;code>enum CountryRoles&lt;/code> strikes back. Let me avoid you some scrolling:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">enum&lt;/span> &lt;span class="nc">CountryRoles&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CapitalRole&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Qt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">UserRole&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here, we defined a custom role, &lt;code>CapitalRole&lt;/code> that takes the &lt;code>Qt::UserRole&lt;/code> value. &lt;code>Qt::UserRole&lt;/code> is simply the starting value (&lt;code>0x0100&lt;/code>) for user defined roles. If you have more data to provide in your Model, just continue to add custom roles in your &lt;code>enum&lt;/code>.&lt;/p>
&lt;p>A last important detail —&lt;code>data()&lt;/code> is &lt;code>const&lt;/code>. In other words, it cannot change&lt;code>CountryModel&lt;/code>, and can only call&lt;code>const&lt;/code> functions. If you want to generate thumbnails on the fly for a country&amp;rsquo;s flag, you&amp;rsquo;re in for a world of pain. By design, the &lt;code>data()&lt;/code> functions expects to retrieve const-data and returns a &lt;code>QVariant&lt;/code> to be copied by value. There are possible workarounds with a &lt;a href="http://doc.qt.io/qt-5/qidentityproxymodel.html">QIdentityProxyModel&lt;/a>. If you&amp;rsquo;re going with QML, the Qt folks were thoughtful with &lt;a href="http://doc.qt.io/qt-5/qquickimageprovider.html">QQuickImageProvider&lt;/a>.&lt;/p>
&lt;h2 id="the-view">The View&lt;/h2>
&lt;p>The View is straightforward, in a usual &lt;code>MainWindow&lt;/code> class, The form looks like this:&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/qt-modelview-read/model-view-read-ui-form.png"
onclick="openLightbox('\/img\/qt-modelview-read\/model-view-read-ui-form.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >UI Form of the MainWindow&lt;/figcaption>
&lt;/figure>
&lt;ul>
&lt;li>On the left, the most important widget is &lt;code>countryListView&lt;/code>, a &lt;code>QListView&lt;/code> that will display the data coming from &lt;code>CountryModel&lt;/code>.&lt;/li>
&lt;li>On the right, &lt;code>countryCapitalLabel&lt;/code> and &lt;code>countryNameLabel&lt;/code> will be updated with the currently selected &lt;code>Country&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>The source is short, starting with &lt;code>MainWindow.h&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#ifndef MAINWINDOW_H
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#define MAINWINDOW_H
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;QMainWindow&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;QItemSelection&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;CountryModel.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="n">Ui&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">    &lt;span class="k">class&lt;/span> &lt;span class="nc">MainWindow&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MainWindow&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="k">public&lt;/span> &lt;span class="n">QMainWindow&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">     &lt;span class="n">Q_OBJECT&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">     &lt;span class="k">explicit&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">QWidget&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">parent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">     &lt;span class="o">~&lt;/span>&lt;span class="n">MainWindow&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span> &lt;span class="nl">slots&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">     &lt;span class="kt">void&lt;/span> &lt;span class="n">onSelectionChanged&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">QItemSelection&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">selected&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">QItemSelection&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">deselected&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">     &lt;span class="n">Ui&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">MainWindow&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">ui&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">     &lt;span class="n">CountryModel&lt;/span> &lt;span class="n">countryModel&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#endif &lt;/span>&lt;span class="c1">// MAINWINDOW_H
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>onSelectionChanged()&lt;/code> will be called each time a selection changes on &lt;code>countryListView&lt;/code>.&lt;/li>
&lt;li>&lt;code>countryModel&lt;/code> is the &lt;code>CountryModel&lt;/code> member variable that will feed the data to &lt;code>countryListView&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>The last piece of the puzzle hides in &lt;code>MainWindow.cpp&lt;/code>, starting with the constructor:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;MainWindow.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;ui_MainWindow.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;QItemSelectionModel&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">MainWindow&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">MainWindow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">QWidget&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QMainWindow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ui&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="n">Ui&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">MainWindow&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">countryModel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ui&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">setupUi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">setWindowTitle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Model / View sample - countries&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ui&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">countryListView&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">setModel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">countryModel&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QItemSelectionModel&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">selectionModel&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ui&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">countryListView&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">selectionModel&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">selectionModel&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">QItemSelectionModel&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">selectionChanged&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">MainWindow&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">onSelectionChanged&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The whole magic happens when &lt;code>countryListView-&amp;gt;setModel(&amp;amp;countryModel)&lt;/code> is called. Because &lt;code>CountryModel&lt;/code> extends &lt;code>QAbstractListModel&lt;/code>, this call is generic from &lt;code>countryListView&lt;/code>&amp;rsquo;s perspective. If we decide to update &lt;code>CountryModel&lt;/code> to store the countries in a database, &lt;code>countryListView&lt;/code> couldn&amp;rsquo;t care less.&lt;/p>
&lt;p>The next lines are here to update our custom country name / capital labels. The selection of a &lt;code>QListView&lt;/code> is also handled by a Model, this time it&amp;rsquo;s a &lt;a href="http://doc.qt.io/qt-5/qitemselectionmodel.html">QItemSelectionModel&lt;/a>. The implementation of &lt;code>onSelectionChanged()&lt;/code> looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">void&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">onSelectionChanged&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">QItemSelection&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">selected&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">QItemSelection&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">deselected&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Q_UNUSED&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">deselected&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QModelIndexList&lt;/span> &lt;span class="n">list&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">selected&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">indexes&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">const&lt;/span> &lt;span class="n">QModelIndex&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">list&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">first&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QString&lt;/span> &lt;span class="n">countryName&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">index&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Qt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">DisplayRole&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">toString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ui&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">countryNameLabel&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">setText&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">countryName&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">QString&lt;/span> &lt;span class="n">countryCapital&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">index&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CountryModel&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">CapitalRole&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">toString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ui&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">countryCapitalLabel&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">setText&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">countryCapital&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The parameters &lt;code>selected&lt;/code> &amp;amp; &lt;code>deselected&lt;/code> encapsulate the current state of the &lt;code>countryListView&lt;/code>. From here, we retrieve the currently selected indexes (&lt;code>QListView&lt;/code> can support multiple selections). We take the first index, and start working with our now-old-friend, &lt;code>QModelIndex&lt;/code>.&lt;/p>
&lt;p>To update &lt;code>countryNameLabel&lt;/code>, we get the data from the Model, using the &lt;code>index.data()&lt;/code> function. Remember &lt;code>CountryModel::data()&lt;/code> with the &lt;code>role&lt;/code> parameter? Here it is, in all its glory! The returned &lt;code>QVariant&lt;/code> is converted to a &lt;code>QString&lt;/code> and then passed to &lt;code>countryNameLabel&lt;/code>. The same pattern is used for &lt;code>countryCapitalLabel&lt;/code>, although we rely on our custom role — &lt;code>CountryModel::CapitalRole&lt;/code> — to target the right piece of data.&lt;/p>
&lt;div style="text-align: center; margin: 2em 0; clear: both;">
&lt;span style="font-size: 1.5em; color: #666;">• • •&lt;/span>
&lt;/div>
&lt;p>As you can see, there&amp;rsquo;s still no trace of &lt;code>CountryModel&lt;/code> around, even though it&amp;rsquo;s working behind the scene. It took &lt;strong>less than 150 lines of C++ code to scaffold a generic data source that can be plugged in a standard &lt;code>QListView&lt;/code>&lt;/strong>. In my humble opinion, it&amp;rsquo;s quite a treat to achieve this much with so little code.&lt;/p>
&lt;p>Again, everything is available on &lt;a href="https://github.com/synapticvoid/sample-qt-model-view">GitHub&lt;/a>, go and take a look.&lt;/p>
&lt;p>The first time I really understood Model / View like so, I wondered. Is the documentation size inversely proportional to the simplicity of the concept it tries to explain? Is it a just a smokescreen, some kind of conspiration? &lt;em>Is life like the Model / View documentation, so simple and yet so hard to explain?&lt;/em>&lt;/p>
&lt;h2 id="going-deeper">Going deeper&lt;/h2>
&lt;p>If you want to dig deeper in Qt, I co-wrote a whole book about the Qt framework, &lt;a href="https://www.packtpub.com/web-development/mastering-qt-5-second-edition">Mastering Qt 5&lt;/a>. The Model / View is covered inside out, with the full data CRUD explanation, QML implementation to connect to a Model — it takes 3 chapters to cover the material.&lt;/p>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/qt-modelview-read/mastering-qt5-cover.png"
onclick="openLightbox('\/img\/qt-modelview-read\/mastering-qt5-cover.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Mastering Qt 5 cover&lt;/figcaption>
&lt;/figure>
&lt;p>The book aims to teach you how to be a well-rounded Qt developer. We took our years of experience, frustrations, discoveries and joys about Qt and poured them into these 500 page. We explore the inner working of the Qt signal / slot system, build advanced multi-threaded applications, explain how Qt can work with C++11/14 and much more.&lt;/p></content></item><item><title>Contact</title><link>https://gryt.io/page/contact/</link><pubDate>Tue, 07 Aug 2018 18:50:08 +0200</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/page/contact/</guid><description>&amp;nbsp; Email — robin@gryt.io x.com — @synapticrob &amp;nbsp; I look forward to discuss with you :)</description><content>&lt;div class="vertical-spacer">&amp;nbsp;&lt;/div>
&lt;h2 id="email--robingrytio">Email — &lt;a href="mailto:robin@gryt.io">robin@gryt.io&lt;/a>&lt;/h2>
&lt;h2 id="xcom--synapticrobhttpsxcomsynapticrob">x.com — &lt;a href="https://x.com/synapticrob">@synapticrob&lt;/a>&lt;/h2>
&lt;div class="vertical-spacer">&amp;nbsp;&lt;/div>
&lt;h3 id="i-look-forward-to-discuss-with-you-">I look forward to discuss with you :)&lt;/h3></content></item><item><title>About</title><link>https://gryt.io/page/about/</link><pubDate>Mon, 09 Jul 2018 10:21:40 +0200</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/page/about/</guid><description>&amp;nbsp; I&amp;rsquo;m Robin Penea I&amp;rsquo;m a passionated software engineer.
To me, software development is a craft. It has to be practiced, again and again. Through trial and error, it improves. With theoretical knowledge, the craft deepens.
I&amp;rsquo;ve been doing it for more than 15 years, and I love it. I worked in small startups and huge corporations. The constraints evolve, but the fundamentals stay the same:
Understand the technical stack: where does it come from?</description><content>&lt;div class="vertical-spacer">&amp;nbsp;&lt;/div>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/robin.jpg"
onclick="openLightbox('\/img\/robin.jpg')"
style="cursor: pointer;" />
&lt;/figure>
&lt;h2 id="im-robin-penea">I&amp;rsquo;m Robin Penea&lt;/h2>
&lt;p>I&amp;rsquo;m a passionated software engineer.&lt;/p>
&lt;p>&lt;em>To me, software development is a craft&lt;/em>. It has to be practiced, again and again. Through trial and error, it improves. With theoretical knowledge, the craft deepens.&lt;/p>
&lt;p>&lt;strong>I&amp;rsquo;ve been doing it for more than 15 years, and I love it.&lt;/strong>
I worked in small startups and huge corporations. The constraints evolve, but the fundamentals stay the same:&lt;/p>
&lt;ul>
&lt;li>&lt;em>Understand the technical stack&lt;/em>: where does it come from?&lt;/li>
&lt;li>&lt;em>Connect with the people&lt;/em>: where do they want to go from here?&lt;/li>
&lt;/ul>
&lt;h3 id="tech">Tech&lt;/h3>
&lt;h4 id="im-the-most-proficient">I&amp;rsquo;m the most proficient&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>Android / Kotlin / Flutter&lt;/strong> — More than 5 years working full-time on the Android OS and created or maintained dozens of applications ;&lt;/li>
&lt;li>&lt;strong>Python / Django&lt;/strong> — Started in 2010 with an entreprise oriented Dropbox-like product, I always integrated DevOps (Linux, Docker, Ansible, CI) as a natural extension of my backend development ;&lt;/li>
&lt;li>&lt;strong>C++ / Qt&lt;/strong> — My attraction to low level graphics with OpenGL / Shaders naturally blended with C++. The encounter with Qt solidified my relationship with this language. It gave birth to a 525 pages book and multiple projects.&lt;/li>
&lt;/ul>
&lt;h4 id="im-ramping-up-my-skills">I&amp;rsquo;m ramping up my skills&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>Language Design&lt;/strong> — Designing my own interpreter and then compiler.&lt;/li>
&lt;li>&lt;strong>Clojure&lt;/strong> — because one needs to study a different thought process to see his craft with new eyes ;&lt;/li>
&lt;li>&lt;strong>Hypermedia websites&lt;/strong> — HTMX got me fired up about REST, HATEOAS, and alternative ways of designing websites&lt;/li>
&lt;/ul>
&lt;h3 id="last-technical-books">Last Technical Books&lt;/h3>
&lt;ul>
&lt;li>&lt;em>Hypermedia systems&lt;/em> — Carson Gross&lt;/li>
&lt;li>&lt;em>High Performance Browser Networking&lt;/em> — Ila Grigorik&lt;/li>
&lt;li>&lt;em>Crafting Interpreters&lt;/em> — Robert Nystrom&lt;/li>
&lt;li>&lt;em>Writing A Compiler In Go&lt;/em> — Thorsten Ball&lt;/li>
&lt;li>&lt;em>Writing An Interpreter In Go&lt;/em> — Thorsten Ball&lt;/li>
&lt;li>&lt;em>Designing Data-Intensive Applications&lt;/em> — Martin Kleppmann&lt;/li>
&lt;li>&lt;em>Getting Clojure&lt;/em> — Russ Olsen&lt;/li>
&lt;li>&lt;em>Clojure for the Brave and True&lt;/em> — Daniel Higginbotham&lt;/li>
&lt;li>&lt;em>Growing Object-Oriented Software, Guided by Tests&lt;/em> — Steve Freeman &amp;amp; Nat Pryce&lt;/li>
&lt;/ul>
&lt;div class="vertical-spacer">&amp;nbsp;&lt;/div>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/gryt-logo-dark-background.png"
onclick="openLightbox('\/img\/gryt-logo-dark-background.png')"
style="cursor: pointer;" />
&lt;/figure>
&lt;h2 id="gryt-is-my-company">Gryt is my company&lt;/h2>
&lt;p>&lt;em>It embodies how I want to practice my craft&lt;/em>: with dedication and attention.&lt;/p>
&lt;p>&lt;strong>Gryt stems from the English word — &lt;em>Grit&lt;/em>.&lt;/strong> It is the combination of perseverance and motivation for a long term goal.&lt;/p>
&lt;h3 id="goal">Goal&lt;/h3>
&lt;p>My goal with Gryt is to &lt;strong>strive for quality&lt;/strong>. There&amp;rsquo;s no such thing as a perfect product. But within the constraints, it&amp;rsquo;s possible to achieve the best &lt;em>possible&lt;/em> product.&lt;/p>
&lt;h3 id="values">Values&lt;/h3>
&lt;p>This goal can only be achieved with practical values. Only then, we&amp;rsquo;re able to measure up our decisions against this long term goal. I founded Gryt with the following principles:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Resilience&lt;/strong> — &lt;em>Learning, Working and Living is hard&lt;/em>. It&amp;rsquo;s a bumpy road and to play the long game, I have to show a lot of grit.&lt;/li>
&lt;li>&lt;strong>Curiosity&lt;/strong> — &lt;em>An open mind unveils unexpected new paths&lt;/em>. The sheer joy to learn new things and the hunger the question my preconceptions ;&lt;/li>
&lt;li>&lt;strong>Quality&lt;/strong> — &lt;em>There&amp;rsquo;s no free lunch&lt;/em>. My customers &lt;em>feel&lt;/em> if the product was crafted with care. Quality attracts quality and appreciation ;&lt;/li>
&lt;/ul></content></item><item><title>Projects</title><link>https://gryt.io/page/projects/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/page/projects/</guid><description>[2020] ANEP Monitoring — ANEP Monitoring of the devices fleet. Development of the mobile app that provides real-time monitoring of the devices (elevators, security doors) fleet. The details of each device can be displayed and it can be called (using ANEP Mobile) for further configuration. ANEP Monitoring map display Detail of a device History of operations for a given device &amp;times; Flutter, Dart, Google Maps (with custom clustering), BLoC pattern, TDD [2019 / 2020] ANEP Mobile — ANEP Elevator alarm configuration tool.</description><content>&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2020]&lt;/span>
ANEP Monitoring
— &lt;em>ANEP&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Monitoring of the devices fleet&lt;/strong>. Development of the mobile app that provides real-time monitoring of the devices (elevators, security doors) fleet. The details of each device can be displayed and it can be called (using ANEP Mobile) for further configuration.
&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/anep/anep_monitoring_01.webp"
onclick="openLightbox('\/img\/projects\/anep\/anep_monitoring_01.webp')"
style="cursor: pointer;" />
&lt;figcaption class="center" >ANEP Monitoring map display&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/anep/anep_monitoring_02.webp"
onclick="openLightbox('\/img\/projects\/anep\/anep_monitoring_02.webp')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Detail of a device&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/anep/anep_monitoring_03.webp"
onclick="openLightbox('\/img\/projects\/anep\/anep_monitoring_03.webp')"
style="cursor: pointer;" />
&lt;figcaption class="center" >History of operations for a given device&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Flutter, Dart, Google Maps (with custom clustering), BLoC pattern, TDD&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2019 / 2020]&lt;/span>
ANEP Mobile
— &lt;em>ANEP&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Elevator alarm configuration tool&lt;/strong>. Development of a server app that calls elevator alarm boxes to configure them. I also developed the Android application, that triggers jobs on the server to interact with the alarm boxes.
&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/anep/anep_mobile_01.png"
onclick="openLightbox('\/img\/projects\/anep\/anep_mobile_01.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >ANEP Mobile keypad screen&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/anep/anep_mobile_02.png"
onclick="openLightbox('\/img\/projects\/anep\/anep_mobile_02.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Alarm numbers configuration&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/anep/anep_mobile_03.png"
onclick="openLightbox('\/img\/projects\/anep\/anep_mobile_03.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Template of a configuration&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Android, Kotlin, MVVM, Python, Django, TDD, SIP (Asterisk)&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2019]&lt;/span>
Solar Mobile
— &lt;em>Atexia Software&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Mobile app for a file management platform&lt;/strong>. Complete rewrite of a mobile application to navigate through directories, download files and display pictures on the Solar System. The app is compatible with iOS and Android thanks to the Flutter technology.
&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/solar/solar_flutter_01.png"
onclick="openLightbox('\/img\/projects\/solar\/solar_flutter_01.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Solar Mobile - List of files&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/solar/solar_flutter_02.png"
onclick="openLightbox('\/img\/projects\/solar\/solar_flutter_02.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Solar Mobile - Detail of a file&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/solar/solar_flutter_03.png"
onclick="openLightbox('\/img\/projects\/solar\/solar_flutter_03.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Solar Mobile - Gallery&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Flutter, Dart&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2017 / 2018]&lt;/span>
Borgward BX7
— &lt;em>Parrot&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Car infotainment system based on Android&lt;/strong>. Development of specific apps (&lt;em>Tuner, Multimedia&lt;/em>). Architecture of the communication service with the car&amp;rsquo;s contructor backend. OS tweaks to enhance the integration inside the vehicle.
&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/parrot/borgward-bx7-exterior.jpg"
onclick="openLightbox('\/img\/projects\/parrot\/borgward-bx7-exterior.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Borgward BX7&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/parrot/borgward-bx7-interior.jpg"
onclick="openLightbox('\/img\/projects\/parrot\/borgward-bx7-interior.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Borgward BX7 Interior&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/parrot/borgward-hud-02.jpg"
onclick="openLightbox('\/img\/projects\/parrot\/borgward-hud-02.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Borgward Car Settings Application&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/parrot/borgward-hud-04.jpg"
onclick="openLightbox('\/img\/projects\/parrot\/borgward-hud-04.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Borgward FM Player Application&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Android, Java, C&amp;#43;&amp;#43;, Bash&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2018]&lt;/span>
DCP / IDConfig
— &lt;em>JCDecaux&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Abribus management system&lt;/strong>. Django backend to manage more than &lt;em>10 000 city furniture configurations&lt;/em>. Development of a client Android Application to let the maintenance operator retrieve furniture data. Identification is done through a configurable &lt;em>Beacon&lt;/em>, &lt;em>NFC Tag&lt;/em> or &lt;em>QRCode&lt;/em>.
&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/idconfig/idconfig-01.png"
onclick="openLightbox('\/img\/projects\/idconfig\/idconfig-01.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >IDConfig Android app&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/idconfig/idconfig-02.png"
onclick="openLightbox('\/img\/projects\/idconfig\/idconfig-02.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >IDConfig Android app&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/idconfig/idconfig-05.png"
onclick="openLightbox('\/img\/projects\/idconfig\/idconfig-05.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >IDConfig Android app&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Python, Django, Android, Kotlin&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2017]&lt;/span>
AutoTune
— &lt;em>Arkamys&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Low latency audio acquisition software&lt;/strong>. Development of a &lt;em>multi-channel audio acquisition&lt;/em> software using the low latency library PortAudio. The captured audio could then be visualized on complex graphs, &lt;em>handling more than 96 000 dots&lt;/em>.
&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/arkamys_plot-708x400.png"
onclick="openLightbox('\/img\/projects\/arkamys_plot-708x400.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >AutoTune sound graph&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> C&amp;#43;&amp;#43;, Qt, PortAudio&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2017]&lt;/span>
PoudlardExpress
— &lt;em>Bilberry&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Weeding system management on a train&lt;/strong>. &lt;em>Multi-threaded, real-time weeding system mounted on a train&lt;/em>. Aggregation of various inputs to trigger nozzles opening on an automatic train:&lt;/p>
&lt;ul>
&lt;li>Exchange of CAN messages with the Train software&lt;/li>
&lt;li>Processing of multiple inputs : weather station, GPS location, Camera&amp;rsquo;s data, &amp;hellip;&lt;/li>
&lt;li>Real-time display of the system state using the D-Bus protocol&lt;/li>
&lt;li>300 ms window to analyze and trigger or not the nozzles
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> C&amp;#43;&amp;#43;, Qt, CAN, ZeroMQ, D-BUS&lt;/em>
&lt;/div>&lt;/li>
&lt;/ul>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2016]&lt;/span>
Scan3D
— &lt;em>Arteractive&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Automatic morphology measurement software&lt;/strong>. With the input data of a &lt;em>Kinect Explorer&lt;/em>, the software extracted body morphology data from analyzed slices using custom OpenGL code and OpenCV filtering.
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> C&amp;#43;&amp;#43;, Qt, OpenCV, OpenGL, Kinect&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2016 / 2017]&lt;/span>
Atelier 27
— &lt;em>Eram &amp;amp; Unistudio&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>3D printed shoe heel with a tablet configurator&lt;/strong>. A unique way of customizing a shoe heel: visualization and creation of a &lt;em>unique heel before printing it in live with a 3D printer&lt;/em>.&lt;/p>
&lt;p>The project was presented during the exhibition Equip’Mag in Paris mid-september 2016 and the VAD Conext in Lille mid-october 2016. Several medias covered the announce like &lt;strong>Les Echos, Le Monde, M6, BFM TV, Europe 1, Francer Inter, France Bleue&lt;/strong> and many more.
&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/atelier27/atelier27-project.jpg"
onclick="openLightbox('\/img\/projects\/atelier27\/atelier27-project.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Atelier 27 Project&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/atelier27/configurateur3d-tablet.png"
onclick="openLightbox('\/img\/projects\/atelier27\/configurateur3d-tablet.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >3D Configurator&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/atelier27/unistudio-eram-atelier27-cover-talon.jpg"
onclick="openLightbox('\/img\/projects\/atelier27\/unistudio-eram-atelier27-cover-talon.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >3D Printed Shoe Heel&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> WebGL, JavaScript, Python, Django, Android, 3D printing&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2013 - 2016]&lt;/span>
Livetouch
— &lt;em>JCDecaux&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Lead dev on an Android OS fork for outdoor 55&amp;quot; furnitures&lt;/strong>. I migrated the Android framework &lt;em>from the version 4.0.4 to the 4.3 (C++ and Java middleware)&lt;/em>. The Android behavior was deeply modified to match a public outdoor usage (custom app lifecycle, remote-control modules). My work also included:&lt;/p>
&lt;ul>
&lt;li>Integration of peripherals : Barcode scanner, NFC, VoIP handset&lt;/li>
&lt;li>Global project Architecture with Python scripting, Git workflow&lt;/li>
&lt;li>Maintenance and release of a custom SDK for third-party developers&lt;/li>
&lt;li>Continuous Build system with Jenkins jobs&lt;/li>
&lt;/ul>
&lt;p>&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/livetouch/jcdecaux-01.jpg"
onclick="openLightbox('\/img\/projects\/livetouch\/jcdecaux-01.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Livetouch Abribus installation&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/livetouch/jcdecaux-02.jpg"
onclick="openLightbox('\/img\/projects\/livetouch\/jcdecaux-02.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Livetouch Abribus installation&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/livetouch/jcdecaux-03.jpg"
onclick="openLightbox('\/img\/projects\/livetouch\/jcdecaux-03.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Livetouch Android Launcher&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/livetouch/livetouch_1.jpg"
onclick="openLightbox('\/img\/projects\/livetouch\/livetouch_1.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Live view on remotely controlled devices&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/livetouch/livetouch_2.jpg"
onclick="openLightbox('\/img\/projects\/livetouch\/livetouch_2.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Map of outdoor devices&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Android, Java, C&amp;#43;&amp;#43;, Git, Bash, Python&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2013]&lt;/span>
NeuronalMind42
— &lt;em>Mind42&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Developer of a client application to manage MindMaps&lt;/strong>. I was a user of the &lt;a href="https://mind42.com/">mind42.com website&lt;/a> and wanted to access and edit mindmaps from my Android phone. After discussing with the owner of mind42, we agreed on a developer API and I developed and published with a friend the NeuronalMind42 Android application. It features:&lt;/p>
&lt;ul>
&lt;li>Custom drawing of the mindmaps (pictures, font, colors, icons)&lt;/li>
&lt;li>Full MindMaps access and edition&lt;/li>
&lt;li>Offline access of MindMaps&lt;/li>
&lt;li>Collaborative access to a MindMap with live updates&lt;/li>
&lt;/ul>
&lt;p>&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/neuronalmind42/mind42-01.jpg"
onclick="openLightbox('\/img\/projects\/neuronalmind42\/mind42-01.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >MindMap in NeuronalMind42&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/neuronalmind42/mind42-02.jpg"
onclick="openLightbox('\/img\/projects\/neuronalmind42\/mind42-02.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >List of MindMaps in NeuronalMind42&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/neuronalmind42/mind42-03.jpg"
onclick="openLightbox('\/img\/projects\/neuronalmind42\/mind42-03.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Tablet display in NeuronalMind42&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/neuronalmind42/mind42-04.jpg"
onclick="openLightbox('\/img\/projects\/neuronalmind42\/mind42-04.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Media content in NeuronalMind42&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Android, Java&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2013]&lt;/span>
Mobile apps for Leclerc / Crédit Agricole
— &lt;em>Ezeeworld&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Heavy work on the mobile apps of Leclerc &amp;amp; Crédit Agricole&lt;/strong>. I revamped the Android Leclerc app &lt;em>Qui est le moins cher&lt;/em> to update it with the latest UI guidelines. I also squashed, many, many bugs.
&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/ezeeworld/qui-est-le-moins-cher-01.jpg"
onclick="openLightbox('\/img\/projects\/ezeeworld\/qui-est-le-moins-cher-01.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Leclerc - Qui est le moins cher ?&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/ezeeworld/qui-est-le-moins-cher-02.jpg"
onclick="openLightbox('\/img\/projects\/ezeeworld\/qui-est-le-moins-cher-02.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Leclerc - Qui est le moins cher ?&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Java, Android, Objective-C, iOS, git&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2012 - 2013]&lt;/span>
Solar
— &lt;em>Atexia Software&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Lead dev of a Dropbox like product: Solar&lt;/strong>. I was involved in the development of a full backend that managed user files and virtual objects. To name a few features:&lt;/p>
&lt;ul>
&lt;li>Automatic PDF conversion&lt;/li>
&lt;li>Automatic generation of previews (full sized picture and PDF)&lt;/li>
&lt;li>Multiple file upload&lt;/li>
&lt;li>Zip download of directories&lt;/li>
&lt;li>User statistics with graphs of the system usage&lt;/li>
&lt;/ul>
&lt;p>The system was automatically deployed with custom scripts Bash scripts and Ansible rules. I also developed a full-featured Android application with tablet support, push messaging, and maintained the iOS app.
&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/solar/solarweb-01.png"
onclick="openLightbox('\/img\/projects\/solar\/solarweb-01.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Solar Web Interface&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/solar/solarweb-02.png"
onclick="openLightbox('\/img\/projects\/solar\/solarweb-02.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Solar Web Interface&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/solar/solar-01.jpg"
onclick="openLightbox('\/img\/projects\/solar\/solar-01.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Solar Android app&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/solar/solar-02.png"
onclick="openLightbox('\/img\/projects\/solar\/solar-02.png')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Solar Android app&lt;/figcaption>
&lt;/figure>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/solar/solar-03.jpg"
onclick="openLightbox('\/img\/projects\/solar\/solar-03.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Solar Android app&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> C&amp;#43;&amp;#43;, Java, Android, Bash, Python, Django, Ansible, Objective-C&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2010 - 2012]&lt;/span>
DECT Android Phone
— &lt;em>Sagemcom&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>Android OS fork maintenance to build a DECT Android Tablet Phone&lt;/strong>. The Android RIL with the GSM implementation was replaced by the DECT protocol. Heavy modifications of the Phone and Contact app to support a split view, custom UI. Implementation and integration of the accelerometer driver in the Linux Kernel.
&lt;div class="gallery">
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/projects/sagemcom/sagemcom-dect-tablet.jpg"
onclick="openLightbox('\/img\/projects\/sagemcom\/sagemcom-dect-tablet.jpg')"
style="cursor: pointer;" />
&lt;figcaption class="center" >Sagemcom DECT Android Tablet&lt;/figcaption>
&lt;/figure>
&lt;/div>
&lt;div id="lightbox" class="lightbox">
&lt;span class="lightbox-close" onclick="closeLightbox()">&amp;times;&lt;/span>
&lt;img class="lightbox-content" id="lightbox-img">
&lt;div class="lightbox-caption" id="lightbox-caption">&lt;/div>
&lt;/div>
&lt;script>
function openLightbox(src) {
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const lightboxCaption = document.getElementById('lightbox-caption');
lightbox.style.display = 'block';
lightboxImg.src = src;
const clickedImg = document.querySelector(`img[src="${src}"]`);
const caption = clickedImg.parentNode.querySelector('figcaption');
lightboxCaption.innerHTML = caption ? caption.innerHTML : '';
}
function closeLightbox() {
document.getElementById('lightbox').style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function() {
const lightbox = document.getElementById('lightbox');
if (lightbox) {
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closeLightbox();
}
});
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
&lt;/script>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> C&amp;#43;&amp;#43;, Java, Android Framework, Linux Kernel, Bash, Python, git&lt;/em>
&lt;/div>&lt;/p>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2007 - 2010]&lt;/span>
IP Camera Manager
— &lt;em>Wasatis&lt;/em>
&lt;/h3>
&lt;/div>
&lt;strong>IP Camera Management system&lt;/strong>. Remote control of IP Camera for security purposes. I developed a custom motion detector using C++ and OpenCV to trigger alerts and recording on a backend. I was heavily involved in the backend development in PHP, which stored thousands of records and let the user:&lt;/p>
&lt;ul>
&lt;li>Schedule periodic or one-time recordings&lt;/li>
&lt;li>Navigate through recording, frame by frame or using a video player&lt;/li>
&lt;li>Access remotely to the IP Cameras with a live view and perform actions (Pan, Tilt, Zoom)
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> C&amp;#43;&amp;#43;, OpenCV, Java, Linux, Embedded software, Perl, PHP 5, SQL&lt;/em>
&lt;/div>&lt;/li>
&lt;/ul></content></item><item><title>Teaching</title><link>https://gryt.io/page/teaching/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>robin@gryt.io (Robin Penea)</author><guid>https://gryt.io/page/teaching/</guid><description>IT School I teach programming courses in an innovative IT School: Coding Factory. This school offers an alternative pedagogy based on the PBL method (Project Based Learning), the eduScrum.
I teach from Bachelor to Master&amp;rsquo;s degree on many topics: Algorithms, Python, Java, Android, Linux.
Company Training I provide technical training on the following topics:
Django Kotlin The training is done on-site (France / Belgium / Switzerland area), directly in your company&amp;rsquo;s office.</description><content>&lt;hr>
&lt;h2 id="it-school">IT School&lt;/h2>
&lt;p>I teach programming courses in an innovative IT School: &lt;a href="https://codingfactory.fr/">Coding Factory&lt;/a>. This school offers an alternative pedagogy based on the PBL method (Project Based Learning), the &lt;a href="https://www.eduscrum.nl/">eduScrum&lt;/a>.&lt;/p>
&lt;p>I teach from Bachelor to Master&amp;rsquo;s degree on many topics: Algorithms, Python, Java, Android, Linux.&lt;/p>
&lt;h2 id="company-training">Company Training&lt;/h2>
&lt;p>I provide technical training on the following topics:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.ambient-it.net/formation/formation-django/">Django&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.humancoders.com/formations/kotlin">Kotlin&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>The training is done on-site (France / Belgium / Switzerland area), directly in your company&amp;rsquo;s office.&lt;/p>
&lt;p>My goal is to make the attending developers &lt;strong>proficient with the technology&lt;/strong>, and more than anything, &lt;strong>ready to build efficient, maintainable applications&lt;/strong>.
&lt;br/>
&lt;br/>&lt;/p>
&lt;div class="vertical-spacer">&amp;nbsp;&lt;/div>
&lt;h2 id="mooc">MOOC&lt;/h2>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2017 - 2020]&lt;/span>
Android / Kotlin
— &lt;em>Udemy&lt;/em>
&lt;/h3>
&lt;/div>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/teaching/android-kotlin-promo.jpg"
onclick="openLightbox('\/img\/teaching\/android-kotlin-promo.jpg')"
style="cursor: pointer;" />
&lt;/figure>
&lt;/p>
&lt;h4 id="summary">Summary&lt;/h4>
&lt;p>&lt;strong>Beginner to advance Kotlin course on Android&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>Introduction to Kotlin (if / else, for, while)&lt;/li>
&lt;li>Object Oriented Programming with Kotlin&lt;/li>
&lt;li>Advanced Kotlin concepts: lambda, sealed class, lazy init, high order functions&lt;/li>
&lt;li>Structure of an Android Application&lt;/li>
&lt;li>Android fragment, database, permissions handling&lt;/li>
&lt;li>Full example project: Weather&lt;/li>
&lt;/ul>
&lt;h4 id="stats">Stats&lt;/h4>
&lt;ul>
&lt;li>164 sessions&lt;/li>
&lt;li>2 full projects&lt;/li>
&lt;li>19h30 hours of content&lt;/li>
&lt;/ul>
&lt;p>If you plan to buy the course, you can use this referral link. &lt;em>By using this link, I&amp;rsquo;ll get a bigger share of the selling price (100% vs 50% at best).&lt;/em>&lt;/p>
&lt;h3 id="course-pagehttpswwwudemycomcourseandroid-kotlin-apps-mobilesreferralcode2a2096c9eeb35b9bef77">&lt;a href="https://www.udemy.com/course/android-kotlin-apps-mobiles/?referralCode=2A2096C9EEB35B9BEF77">Course page&lt;/a>&lt;/h3>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Android, Kotlin&lt;/em>
&lt;/div>
&lt;hr>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2018]&lt;/span>
Android Architecture Components
— &lt;em>Udemy&lt;/em>
&lt;/h3>
&lt;/div>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/teaching/arch-comps-promo.png"
onclick="openLightbox('\/img\/teaching\/arch-comps-promo.png')"
style="cursor: pointer;" />
&lt;/figure>
&lt;/p>
&lt;h4 id="summary-1">Summary&lt;/h4>
&lt;p>&lt;strong>Discover the Google Architecture Components to develop the new Android Apps generation&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>Lifecyle&lt;/li>
&lt;li>LiveData&lt;/li>
&lt;li>ViewModel&lt;/li>
&lt;li>Room&lt;/li>
&lt;li>WorkManager&lt;/li>
&lt;li>Full example project: Bookstore with offline data using the MVVM Design Pattern&lt;/li>
&lt;/ul>
&lt;h4 id="stats-1">Stats&lt;/h4>
&lt;ul>
&lt;li>69 sessions&lt;/li>
&lt;li>5 exercices + a full project&lt;/li>
&lt;li>6 hours of content&lt;/li>
&lt;/ul>
&lt;p>If you plan to buy the course, you can use this referral link. &lt;em>By using this link, I&amp;rsquo;ll get a bigger share of the selling price (100% vs 50% at best).&lt;/em>&lt;/p>
&lt;h3 id="course-pagehttpswwwudemycomcourseandroid-kotlin-developpez-des-apps-next-genreferralcodee921589e5804d61ceef1">&lt;a href="https://www.udemy.com/course/android-kotlin-developpez-des-apps-next-gen/?referralCode=E921589E5804D61CEEF1">Course page&lt;/a>&lt;/h3>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Android, Kotlin&lt;/em>
&lt;/div>
&lt;hr>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2019]&lt;/span>
Android Sensors : Cas pratiques d&amp;#39;applications
— &lt;em>Udemy&lt;/em>
&lt;/h3>
&lt;/div>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/teaching/android-sensors-promo.jpg"
onclick="openLightbox('\/img\/teaching\/android-sensors-promo.jpg')"
style="cursor: pointer;" />
&lt;/figure>
&lt;/p>
&lt;h4 id="summary-2">Summary&lt;/h4>
&lt;p>&lt;strong>Develop 3 fully featured Android application by using the latest concepts in Kotlin&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>MVVM Pattern: ViewModel, Lifecycle, LiveData&lt;/li>
&lt;li>Many sensors GPS, NFC, QRCode, Camera&lt;/li>
&lt;li>Advanced concepts: testing, custom animations&lt;/li>
&lt;li>3 applications about : Google Maps, NFC and QRCode, Camera&lt;/li>
&lt;/ul>
&lt;h4 id="stats-2">Stats&lt;/h4>
&lt;ul>
&lt;li>209 sessions&lt;/li>
&lt;li>3 full projects&lt;/li>
&lt;li>21 hours of content&lt;/li>
&lt;/ul>
&lt;p>If you plan to buy the course, you can use this referral link. &lt;em>By using this link, I&amp;rsquo;ll get a bigger share of the selling price (100% vs 50% at best).&lt;/em>&lt;/p>
&lt;h3 id="course-pagehttpswwwudemycomcourseandroid-sensors-cas-pratiques-dapplicationsreferralcode7532fe89d373e27a7a14">&lt;a href="https://www.udemy.com/course/android-sensors-cas-pratiques-dapplications/?referralCode=7532FE89D373E27A7A14">Course page&lt;/a>&lt;/h3>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Android, Kotlin&lt;/em>
&lt;/div>
&lt;hr>
&lt;h2 id="online">Online&lt;/h2>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2018]&lt;/span>
The Go Language
— &lt;em>Udemy&lt;/em>
&lt;/h3>
&lt;/div>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/teaching/golang-promo.jpg"
onclick="openLightbox('\/img\/teaching\/golang-promo.jpg')"
style="cursor: pointer;" />
&lt;/figure>
&lt;/p>
&lt;h4 id="summary-3">Summary&lt;/h4>
&lt;p>&lt;strong>Discover the fundamentals of the Go language, invented by Google.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Overview of all important concepts of Go : Structs, Interfaces, Goroutines&amp;hellip;&lt;/li>
&lt;li>Unit tests in Go&lt;/li>
&lt;li>Third-party libraries integration&lt;/li>
&lt;li>5 projects to practice every aspect of the language&lt;/li>
&lt;/ul>
&lt;h4 id="stats-3">Stats&lt;/h4>
&lt;ul>
&lt;li>140 sessions&lt;/li>
&lt;li>Exercices and quiz&lt;/li>
&lt;li>11 hours of content&lt;/li>
&lt;/ul>
&lt;p>If you plan to buy the course, you can use this referral link. &lt;em>By using this link, I&amp;rsquo;ll get a bigger share of the selling price (100% vs 50% at best).&lt;/em>&lt;/p>
&lt;h3 id="course-pagehttpswwwudemycomcoursele-langage-go-formation-completereferralcode8d4efd1c74b881faf684">&lt;a href="https://www.udemy.com/course/le-langage-go-formation-complete/?referralCode=8D4EFD1C74B881FAF684">Course page&lt;/a>&lt;/h3>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Go&lt;/em>
&lt;/div>
&lt;hr>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2018]&lt;/span>
Django / Python
— &lt;em>Udemy&lt;/em>
&lt;/h3>
&lt;/div>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/teaching/python-django-promo.jpg"
onclick="openLightbox('\/img\/teaching\/python-django-promo.jpg')"
style="cursor: pointer;" />
&lt;/figure>
&lt;/p>
&lt;h4 id="summary-4">Summary&lt;/h4>
&lt;p>&lt;strong>Beginner to intermediate web development course with Django&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>Python Crash-course&lt;/li>
&lt;li>Overview of all Django&amp;rsquo;s major concepts: views, models, forms, routing&lt;/li>
&lt;li>Page design with Bootstrap 4&lt;/li>
&lt;li>Full example project: a personal Blog&lt;/li>
&lt;/ul>
&lt;h4 id="stats-4">Stats&lt;/h4>
&lt;ul>
&lt;li>114 sessions&lt;/li>
&lt;li>15 exercices + a full project&lt;/li>
&lt;li>10 hours of content&lt;/li>
&lt;/ul>
&lt;p>If you plan to buy the course, you can use this referral link. &lt;em>By using this link, I&amp;rsquo;ll get a bigger share of the selling price (100% vs 50% at best).&lt;/em>&lt;/p>
&lt;h3 id="course-pagehttpswwwudemycomcoursedjango-2-python-maitrisez-le-developpement-webreferralcodecf45c2e0e45a3d1ea682">&lt;a href="https://www.udemy.com/course/django-2-python-maitrisez-le-developpement-web/?referralCode=CF45C2E0E45A3D1EA682">Course page&lt;/a>&lt;/h3>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Python 3, Django 2, Bootstrap 4&lt;/em>
&lt;/div>
&lt;hr>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2020]&lt;/span>
Linux / Shell scripting
— &lt;em>Udemy&lt;/em>
&lt;/h3>
&lt;/div>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/teaching/linux-promo.jpg"
onclick="openLightbox('\/img\/teaching\/linux-promo.jpg')"
style="cursor: pointer;" />
&lt;/figure>
&lt;/p>
&lt;h4 id="summary-5">Summary&lt;/h4>
&lt;p>&lt;strong>Discover Linux and the joys of Shell scriping&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>Install a VirtualBox VM with Ubuntu&lt;/li>
&lt;li>Overview of the Linux OS : filesystem, CLI&lt;/li>
&lt;li>Write shell scripts to automate tasks&lt;/li>
&lt;li>Manage processes with signals&lt;/li>
&lt;/ul>
&lt;h4 id="stats-5">Stats&lt;/h4>
&lt;ul>
&lt;li>55 sessions&lt;/li>
&lt;li>12 exercices&lt;/li>
&lt;li>5h30 hours of content&lt;/li>
&lt;/ul>
&lt;p>If you plan to buy the course, you can use this referral link. &lt;em>By using this link, I&amp;rsquo;ll get a bigger share of the selling price (100% vs 50% at best).&lt;/em>&lt;/p>
&lt;h3 id="course-pagehttpswwwudemycomcourselinux-fondamentaux-et-scripting-shellreferralcodedbc0a3cb4c5714bf7898">&lt;a href="https://www.udemy.com/course/linux-fondamentaux-et-scripting-shell/?referralCode=DBC0A3CB4C5714BF7898">Course page&lt;/a>&lt;/h3>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Linux, CLI, Bash&lt;/em>
&lt;/div>
&lt;hr>
&lt;h2 id="book">Book&lt;/h2>
&lt;p>&lt;div class="project-item">
&lt;h3 class="project-title">
&lt;span class="project-date">[2018]&lt;/span>
Mastering Qt 5
— &lt;em>Packt Publishing&lt;/em>
&lt;/h3>
&lt;/div>
&lt;figure class="left" >
&lt;img src="https://gryt.io/img/teaching/mastering-qt5-cover.png"
onclick="openLightbox('\/img\/teaching\/mastering-qt5-cover.png')"
style="cursor: pointer;" />
&lt;/figure>
&lt;/p>
&lt;h4 id="summary-6">Summary&lt;/h4>
&lt;p>&lt;strong>Advanced development with Qt 5 / C++&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>Create stunning UIs with Qt Widget and Qt Quick&lt;/li>
&lt;li>Use C++ 11/14 with the Qt workflow&lt;/li>
&lt;li>Integrate third-party libraries (e.g. OpenCV) in your cross-plateform app&lt;/li>
&lt;li>Deep dive in Threading, IPC mechanisms, serialization&lt;/li>
&lt;/ul>
&lt;h4 id="stats-6">Stats&lt;/h4>
&lt;ul>
&lt;li>526 pages&lt;/li>
&lt;li>14 chapters&lt;/li>
&lt;li>12 example projects&lt;/li>
&lt;/ul>
&lt;h3 id="book-pagehttpswwwpacktpubcomweb-developmentmastering-qt-5-second-edition">&lt;a href="https://www.packtpub.com/web-development/mastering-qt-5-second-edition">Book page&lt;/a>&lt;/h3>
&lt;div class="tech-subtitle">
&lt;i class="tech-icon fa fa-cogs fa-lg">&lt;/i>&lt;em> Qt, C&amp;#43;&amp;#43;&lt;/em>
&lt;/div></content></item></channel></rss>