<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Stuff I Learned]]></title><description><![CDATA[Stuff I Learned]]></description><link>https://blog.kbremner.com</link><image><url>https://cdn.hashnode.com/uploads/logos/69c983695282e6ac9ce93eb5/a9b0eafa-d5f9-4c72-acff-43cc61d4f8b5.png</url><title>Stuff I Learned</title><link>https://blog.kbremner.com</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 10 Apr 2026 01:51:53 GMT</lastBuildDate><atom:link href="https://blog.kbremner.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[2025 Agentic Developer Tooling - A Guide]]></title><description><![CDATA[Recently I’ve been trying out different types of AI tooling, and it’s incredible just how much the AI developer tooling space has changed in 2025.
Just a year ago, I was using Copilot to finish a function or add some tests. Now? We've got AI “agents”...]]></description><link>https://blog.kbremner.com/2025-agentic-developer-tooling-a-guide</link><guid isPermaLink="true">https://blog.kbremner.com/2025-agentic-developer-tooling-a-guide</guid><category><![CDATA[AI]]></category><category><![CDATA[Developer Tools]]></category><category><![CDATA[mcp]]></category><category><![CDATA[llm]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Mon, 25 Aug 2025 07:13:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816284874/8764d4bb-820e-404d-a975-fbcd5a8cbed0.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently I’ve been trying out different types of AI tooling, and it’s incredible just how much the AI developer tooling space has changed in 2025.</p>
<p>Just a year ago, I was using Copilot to finish a function or add some tests. Now? We've got AI “agents” that can navigate entire codebases, coordinate with each other, run tests, and push PRs—all while we're grabbing coffee. The shift from simple text generation to autonomous development assistants has happened in a flash, and developers are scrambling to get up to speed.</p>
<p>The good news is that beneath all this complexity, there are a handful of core patterns and concepts emerging. Once you grasp them, you can evaluate any new tool that appears next week and understand where it might add value in your workflow.</p>
<h2 id="heading-terminology-that-matters">Terminology That Matters</h2>
<p>First, let's decode some jargon.</p>
<h4 id="heading-agentic-vs-generative-ai">Agentic vs Generative AI</h4>
<p>Generative AI is what we started with: you prompt, it responds, done. It's a one-way transaction. Agentic AI can make decisions, store knowledge, work with tools, and iterate to get the job done. It has goals and can figure out how to achieve them.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816286898/6657095c-71b4-4587-aa70-63bbe70b7653.png" alt /></p>
<h4 id="heading-tool-usefunction-calling">Tool Use/Function Calling</h4>
<p>Instead of just telling you what to do, agents can run commands, call APIs, modify files, search the web, etc. Not all models are great at knowing how and when to use tools, but that’s improving.</p>
<h4 id="heading-mcp-model-context-protocol">MCP (Model Context Protocol)</h4>
<p>A universal adapter for AI assistants to access external data, tools, etc. Each MCP “server” provides a variety of tools. Examples include searching AWS docs, fetching Figma designs, accessing Confluence/Jira, controlling browsers - the list goes on. MCP servers increase what agents can do.</p>
<h4 id="heading-reasoning-models">Reasoning Models</h4>
<p>These AI models "think" before responding. They work through problems step-by-step, and consider different approaches. High levels of reasoning ability are great for any task requiring careful analysis and planning. However, more reasoning generally means higher costs and slower responses.</p>
<h4 id="heading-multi-agent-orchestration">Multi-agent orchestration</h4>
<p>Modern tooling allows us to create a team of specialist agents. For example, you could have one that excels at research, another at writing, another at fact-checking, etc. They might use different models to balance cost with reasoning ability. An orchestrator can come up with a plan to make use of these different agent types, like any other tool, to achieve an objective.</p>
<h4 id="heading-steering-files">Steering Files</h4>
<p>Guidance given to an agent. This can be personal, project, or org level preferences or context (e.g. “be direct and to the point”, “this project is responsible for x, y, and z”, “prefer composition over inheritence”).</p>
<h4 id="heading-context-windows">Context Windows</h4>
<p>The agent’s working memory for a conversation/session, measured in tokens (chunks of words). The agent will consider this context along with a given prompt. Current models range from 8K tokens (about 6,000 words) to 200K+ tokens (a small book). The more steering files, MCP servers, etc that are configured, the less space there is for the history of a conversation. Some tools support reviewing the context and condensing it to enable a conversation to continue indefinitely.</p>
<h2 id="heading-how-it-all-fits-together">How It All Fits Together</h2>
<p>Now that we've decoded the terminology, let's see how these pieces actually work together in modern agentic development tools.</p>
<p>When engaging with modern agentic tooling, we can think of 3 main areas:</p>
<ol>
<li><strong>User interfaces</strong> - how do we interact with agents?</li>
<li><strong>Processing layer</strong> - how does an agent get the job done?</li>
<li><strong>Context sources</strong> - how does an agent know preferences, approaches to avoid, etc?</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816288960/c2f6a035-5ae9-4698-af5e-d0bdac2e534f.png" alt /></p>
<h3 id="heading-user-interfaces-where-you-meet-the-ai">User Interfaces - Where You Meet the AI</h3>
<p>The interface layer has fragmented into distinct modalities, each optimized for different workflows. <strong>IDEs</strong> like Cursor, Windsurf, and Continue have become the default for many developers—they see your code changes in real-time and can respond contextually as you work. <strong>IDE Extensions</strong> (Cline, RooCode, Copilot) plug into your existing environment, adding AI capabilities without requiring a complete switch.</p>
<p><strong>CLI Tools</strong> represent a different philosophy—they're IDE-agnostic and scriptable. Aider, Claude Code, and Q Developer work from the terminal, making them perfect for automation, CI/CD integration, or developers who want to keep agentic AI seperate from their IDE. <strong>Remote agents</strong> like Codex, Claude Code (in autonomous mode), and Jules take this further—they can work independently on tasks while you focus elsewhere. They can be assigned tickets to implement, or PRs to review, and be left to get on with it.</p>
<p>The key insight: you're not locked into one interface. Many developers use Cursor for rapid iteration during development, CLI tools for larger tasks, and remote agents for handling well defined tasks and PR reviews.</p>
<h3 id="heading-processing-layer-the-brain-of-the-operation">Processing Layer - The Brain of the Operation</h3>
<p>This is where the magic happens. When you make a request, it flows through several sophisticated systems working in concert.</p>
<p><strong>Tools</strong> are the hands of the system—they read documentation, control browsers, fetch designs, interact with APIs, and manipulate files. MCP servers have standardized how these tools are exposed, meaning an agent can leverage dozens of capabilities from AWS documentation to Figma designs.</p>
<p><strong>Model Selection</strong> acts as the economic optimizer. Not every task needs the most powerful (and expensive) model, and depending on whether you have a subscription, are using API keys, or running LLMs locally, some models may be much cheaper than others.. Simple refactoring might go to Claude Haiku or GPT-5-mini, while architectural decisions get routed to reasoning models like Opus 4.1. This can be achieved either by tying specific agent modes to certain models, or using a platform like <a target="_blank" href="https://openrouter.ai/">OpenRouter</a> to dynamically route prompts as needed to optimise for what matters.</p>
<p><strong>Memory</strong> is more nuanced than it appears. It's not just about remembering your current conversation—it's about maintaining context across sessions, understanding project evolution, and knowing what mistakes to avoid. Some tools are experimenting with vector databases for long-term memory, while others focus on optimizing session context. The challenge is balancing rich context (better results) with token consumption (higher costs).</p>
<p><strong>Orchestration</strong> coordinates everything. When you ask for a complex feature, an orchestrator agent mode determines whether to use a single agent or create a plan and coordinate multiple specialists. It manages workflows—should the code be written first then tested, or should tests be written first? Should multiple approaches be explored in parallel? Modern orchestration layers can create a visible todo list or plan, spin up swarms of agents that each handle specific aspects of a problem, then synthesize their outputs.</p>
<h3 id="heading-context-sources-the-foundation-of-understanding">Context Sources - The Foundation of Understanding</h3>
<p>Context is what transforms generic AI into your personalized development assistant. This layer has become increasingly sophisticated in how it aggregates and prioritizes information.</p>
<p><strong>Codebase</strong> context includes not just your files, but git history, dependency graphs, and build configurations. Modern tools build semantic indexes of your code, understanding not just syntax but architectural patterns and relationships. RAG systems make this searchable in natural language—the agent can instantly find "that authentication middleware we wrote last month." Some tools like Claude Code forgo this complexity in favour of using existing approaches (e.g. grep).</p>
<p><strong>Persistent Memory</strong> is the emerging frontier. Mem0, specialized MCP servers, and markdown-based memory systems maintain knowledge across sessions. This includes learned preferences ("always use async/await"), discovered constraints ("the legacy API has a 100ms timeout"), and project evolution ("we're migrating from Redux to Zustand").</p>
<p><strong>Documentation</strong> goes beyond README files. It includes API docs, architecture decision records (ADRs), Jira tickets, Confluence pages, etc. These sources give agents the full context of not just what the code does, but why it was built that way.</p>
<p><strong>Steering Docs</strong> represent explicit guidance at multiple levels. Personal preferences ("be concise, skip the fluff"), project context ("this service handles payment processing"), and organizational standards ("follow our Python style guide") all get considered.</p>
<h3 id="heading-the-flow-in-practice">The Flow in Practice</h3>
<p>What makes this all so powerful is how these layers work together. When you request "add caching to the API endpoints," here's what actually happens:</p>
<p>The request enters through your chosen interface and immediately hits the processing layer. An orchestrator agent mode analyzes the task complexity, creates a todo list and delegates a part of the task. Model selection routes to an appropriate model based on the delegated task. The delegated agent pulls context from all available sources—your codebase to understand current implementation, documentation to know caching requirements, steering docs for your preferred caching approach, and persistent memory for any previous caching-related decisions.</p>
<p>Tools spring into action:</p>
<ol>
<li>The file scanner identifies all API endpoint files</li>
<li>The documentation reader pulls your existing caching policies</li>
<li>A sub-agent generates three caching strategies: Redis, in-memory, and CDN</li>
<li>The orchestrator evaluates each against your constraints</li>
<li>The chosen approach gets implemented with appropriate error handling</li>
<li>Tests are automatically generated and run</li>
</ol>
<p>The agent might spawn sub-agents to explore different caching strategies in parallel. Memory systems track the decisions being made, ready to reference them in future sessions.</p>
<p>This isn't just automation—it's intelligent, context-aware assistance that understands your specific project, preferences, and constraints. Tooling has evolved from simple request-response to a sophisticated system that can truly collaborate on complex development tasks.</p>
<h2 id="heading-where-this-goes-next">Where This Goes Next</h2>
<p>The patterns we're seeing in 2025 are just the beginning. As context windows expand toward millions of tokens, as reasoning models get faster and cheaper, and as orchestration becomes more sophisticated and we see more specialist models, what’s possible will continue to expand.</p>
<p>But here's what won't change: the need for human judgment, creativity, and strategic thinking. These tools amplify our capabilities—they don't replace them. Understanding the architecture and patterns helps you see both the possibilities and limitations.</p>
<p>The developers who thrive won't be those who resist these changes or those who blindly adopt every new tool. They'll be the ones who understand the underlying patterns well enough to make intelligent choices about when and how to leverage AI assistance, going forward.</p>
]]></content:encoded></item><item><title><![CDATA[My Go-to Resources for Developing Accessible Frontends]]></title><description><![CDATA[Accessible software design and development can sometimes end up as a bit of an afterthought. Something to be added, if we have time (if considered at all).
Thankfully, though, there are some really great resources out there that help us see that it d...]]></description><link>https://blog.kbremner.com/my-go-to-resources-for-developing-accessible-frontends</link><guid isPermaLink="true">https://blog.kbremner.com/my-go-to-resources-for-developing-accessible-frontends</guid><category><![CDATA[Accessibility]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Fri, 28 Jan 2022 09:14:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69c983695282e6ac9ce93eb5/786f7c7b-b23a-4e41-9e9f-a683ee904937.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Accessible software design and development can sometimes end up as a bit of an afterthought. Something to be added, if we have time (if considered at all).</p>
<p>Thankfully, though, there are some really great resources out there that help us see that it doesn’t need to take much effort to avoid excluding others from the software we create. In fact, it makes good business sense to avoid doing so.</p>
<p>In this post, I highlight a few resources around accessibility that I find myself going back to again and again.</p>
<h4 id="heading-microsoft-inclusive-designhttpswwwmicrosoftcomdesigninclusive"><a target="_blank" href="https://www.microsoft.com/design/inclusive/">Microsoft Inclusive Design</a></h4>
<p>To me, the “Inclusive 101” toolkit at the link above was foundational in helping me understand the importance of designing and developing software with accessibility in mind.</p>
<p>This diagram was particularly insightful:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816477777/7e250014-7f74-4212-942b-5cec26fd30a8.png" alt="A diagram called the “Persona Spectrum”, showing how touch, seeing, hearing and speaking can all be" class="image--center mx-auto" /></p>
<p>The “Persona Spectrum” helps us understand that we can all find ourselves in situations that affect our ability to perform certain tasks</p>
<p>This framing helped me to understand that we can all find ourselves in situations that impair our ability to carry out everyday tasks. As mentioned in the toolkit, greater than 20 million people in the US could be suffering from a permanent, temporary or situational impairment at any one time. When put that way, it’s clear to see the business case for building accessible software.</p>
<p>A designer I once worked with also helped me see that we should be careful not to just consider physical impairments. Take the new parent example above. A physical impairment they might face is that they often only have one hand free. But they could also be dealing with a lack of sleep, or they might be filled with anxiety. If we were developing a site that was used by new parents, we might want to ensure that the site is simple, clear and reassuring, to help with those impairments.</p>
<p>I’d highly recommend that anyone involved in the design and development of software have a look at the resources found at the link above.</p>
<h4 id="heading-web-content-accessibility-guidelines-wcaghttpswwww3orgwaistandards-guidelineswcag"><a target="_blank" href="https://www.w3.org/WAI/standards-guidelines/wcag/">Web Content Accessibility Guidelines (WCAG)</a></h4>
<p>You might think that accessibility guidelines would make for pretty dry reading. Well in some places it can be, but for the most part, WCAG can be easier to find your way around than you might think.</p>
<p>The <a target="_blank" href="https://www.w3.org/WAI/WCAG21/quickref/">quick reference</a> provides a list of each individual guideline, including some interesting ones like <a target="_blank" href="https://www.w3.org/WAI/WCAG21/Understanding/use-of-color.html">this one around the use of colour</a>, or <a target="_blank" href="https://www.w3.org/WAI/WCAG21/Understanding/no-timing.html">this one on avoiding timed interactions</a>. For each guideline, you’ll find information around why the guideline exists, examples of good practice, related resources and some techniques to follow in different situations.</p>
<p>Overall, the guidelines are a worthwhile resource to refer back to.</p>
<h4 id="heading-wai-aria-authoring-practiceshttpswwww3orgtrwai-aria-practices-11"><a target="_blank" href="https://www.w3.org/TR/wai-aria-practices-1.1/">WAI-ARIA Authoring Practices</a></h4>
<p>If you find yourself wondering how to implement a common component pattern like an accordion or modal in an accessible way, these authoring practices are well worth a look. They give guidance on how to use ARIA to help make those components in an accessible way.</p>
<p>The start of the document covers some good principles to be aware of when using ARIA. In particular, there’s the golden rule to always keep in mind — “No ARIA is better than Bad ARIA”.</p>
<p>Covering many commonly used component patterns, for each one the site outlines some tips, what keyboard interactions should be supported, how ARIA should be used to implement that pattern, and an example (for most of them).</p>
<h4 id="heading-accessibility-developer-guidehttpswwwaccessibility-developer-guidecom"><a target="_blank" href="https://www.accessibility-developer-guide.com/">Accessibility Developer Guide</a></h4>
<p>Another great site filled with knowledge and examples around implementing accessible components. The site includes good examples of various common component patterns, but also some common pitfalls that people often run into.</p>
<h4 id="heading-mdn-accessibility-guideshttpsdevelopermozillaorgen-usdocslearnaccessibility"><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility">MDN Accessibility Guides</a></h4>
<p>MDN have some great resources, and the guides around accessibility continue that trend. In particular, <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML">this guide on the importance of semantic HTML</a> is well worth a read.</p>
<h4 id="heading-web-fundamentals-accessibilityhttpsdevelopersgooglecomwebfundamentalsaccessibility"><a target="_blank" href="https://developers.google.com/web/fundamentals/accessibility">Web Fundamentals — Accessibility</a></h4>
<p>The guides at <a target="_blank" href="http://web.dev">web.dev</a> are well worth a read, including those around accessibility. Similar to the MDN guides above, they include a good general overview of web accessibility basics and an introduction to important topics such as focus and semantics.</p>
<h4 id="heading-govuk-design-systemhttpsdesign-systemservicegovuk"><a target="_blank" href="https://design-system.service.gov.uk/">GOV.UK Design System</a></h4>
<p>The GOV.UK design system is often worth a look for inspiration, given the effort that’s been put into making sure that it is accessible.</p>
<p>The <a target="_blank" href="https://design-system.service.gov.uk/components/">components</a> give some examples of implementing common component patterns in an accessible way. The <a target="_blank" href="https://design-system.service.gov.uk/patterns">patterns</a> also give some suggested guidance around how to handle things like dates, passwords, card details, etc, as well as outlining what research had influenced the guidance.</p>
<p>In a future post, I’ll cover some tools worth using to help with developing accessible frontends.</p>
<p>For now, though, I’d like to leave you with one last point. Language is important. “Are we considering accessibility?” makes it sound like a bar to be striving towards. “Are we being ableist here?” encourages us to consider how we could be excluding people. Next time you find yourself discussing a change, why not give the latter question a go.</p>
<p>What resources around accessibility do you find useful, or find yourself going back to?</p>
]]></content:encoded></item><item><title><![CDATA[The Leadership Books I Still Think About]]></title><description><![CDATA[Non-fiction books aren’t everyone’s cup of tea.
Still, there are a few books that I’ve read over my career so far that have drastically changed my thinking around leadership and setting teams up for success.
Some of these books I find myself suggesti...]]></description><link>https://blog.kbremner.com/the-leadership-books-i-still-think-about</link><guid isPermaLink="true">https://blog.kbremner.com/the-leadership-books-i-still-think-about</guid><category><![CDATA[leadership]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Mon, 08 Nov 2021 09:00:37 GMT</pubDate><content:encoded><![CDATA[<p>Non-fiction books aren’t everyone’s cup of tea.</p>
<p>Still, there are a few books that I’ve read over my career so far that have drastically changed my thinking around leadership and setting teams up for success.</p>
<p>Some of these books I find myself suggesting so often to people, that I figured I should probably put them down in a list somewhere. So here they are! (in no particular order.)</p>
<h3 id="heading-5-dysfunctions-of-a-teamhttpswwwamazoncoukfive-dysfunctions-team-leadership-lencioni-ebookdpb006960lqw"><a target="_blank" href="https://www.amazon.co.uk/Five-Dysfunctions-Team-Leadership-Lencioni-ebook/dp/B006960LQW">5 Dysfunctions of a Team</a></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816461550/7404b46c-651a-4747-beb5-8bd91d962720.jpeg" alt="Image shows the cover for the book “5 Dysfunctions of a Team”, containing a central black and white" /></p>
<p>This was one of the first books I read that really challenged my understanding of what a high-performing team looks like. I’ve read some of Patrick Lencioni’s other books and would recommend having a look at them too, but this remains my favourite of his works.</p>
<p>There are plenty of summaries that go into what the 5 dysfunctions put forward by this book are. For me, the model helped me to understand the importance of looking for potential red flags in teams. Do people avoid accountability? Are team conversations silent and lack debate? Are people generally not engaged in the results or success of the team?</p>
<p>The 5 dysfunctions sit in a hierarchy, with trust being at the base. Without trust, a team has no foundation to build upon. Simple I know, but also powerful. Next time you see a team struggling with some of the issues above, have a look for examples of trust. If there aren’t any to be found, building trust would be a good place to start.</p>
<h3 id="heading-turn-the-ship-aroundhttpswwwamazoncoukturn-ship-around-building-breaking-ebookdpb015qq10he"><a target="_blank" href="https://www.amazon.co.uk/Turn-Ship-Around-Building-Breaking-ebook/dp/B015QQ10HE">Turn the Ship Around</a></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816463265/712719ff-c1f7-4dfa-a63b-7703ca8ca3b0.jpeg" alt="Image shows the cover for the book “Turn the Ship Around”, containing the books title above the" /></p>
<p>I particularly like books that offer examples and personal experiences (as you’ll see by some of the other titles in this list).</p>
<p>David Marquet was a submarine commander in the US Navy, and this book recounts his experience trying to practice <a target="_blank" href="https://www.mindtools.com/pages/article/servant-leadership.htm">servant leadership</a>.</p>
<p>That process isn’t a wonderous success story. The author goes through their initial attempts to practice servant leadership that ultimately failed, and the subsequent learnings from those failures.</p>
<p>This book was my first exposure to the idea of servant leadership, after someone did a talk summarising this book at a local meetup. That talk was a revelation for me, at a time when I was struggling with my first forays into leadership. As such, I still recommend this book to those beginning to dip their toe into a role where they are leading others.</p>
<h3 id="heading-team-of-teamshttpswwwamazoncoukteam-teams-rules-engagement-complex-ebookdpb015yvrkpm"><a target="_blank" href="https://www.amazon.co.uk/Team-Teams-Rules-Engagement-Complex-ebook/dp/B015YVRKPM">Team of Teams</a></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816465054/6be18a92-3670-46d9-95d8-f67324eb37bd.jpeg" alt="Image shows the cover for the book “Team of Teams”" /></p>
<p>While fighting Al-Qaeda, the US Army faced a dilemma. Their command and control approach was woefully inadequate against the decentralised approach of Al-Qaeda. While the US Army troops waited for information to flow up the chain for a decision to be made, Al-Qaeda had already come and gone.</p>
<p>Sound familiar?</p>
<p>This book helped me to see the difficulty command and control organisations face when competing in a market with more agile companies. With that, came my light bulb moment around the benefit of pushing decision making down to those closest to the decision. Pushing decision making down, focussing on communicating objectives over methods, means that teams have the authority to change their approach as needed to achieve the required objective.</p>
<p>I’ve referenced this book when managing up as well as when convincing others to embrace the idea of being given objectives over a list of instructions.</p>
<p>(<em>Another book on this topic is <a target="_blank" href="https://www.amazon.co.uk/Escaping-Build-Trap-Effective-Management-ebook/dp/B07K3QBWG1">Escaping the Build Trap</a> (which itself makes some references to <a target="_blank" href="https://www.amazon.co.uk/Art-Action-Leaders-between-Actions-ebook/dp/B01HPVHLHG">The Art of Action</a>), which I haven’t listed as it’s not specifically leadership-focused, but still a great read!</em>).</p>
<h3 id="heading-radical-candorhttpswwwamazoncoukradical-candor-what-want-saying-ebookdpb01lw1lesc"><a target="_blank" href="https://www.amazon.co.uk/Radical-Candor-What-Want-Saying-ebook/dp/B01LW1LESC">Radical Candor</a></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816466717/0b9f32b9-f6f9-4ed7-b690-d98068b977c2.jpeg" alt="Cover for the book “Radical Candor”" /></p>
<p>We’ve all had (and given) <em>that</em> feedback. The type that the receiver walks away from feeling like they didn’t get the full picture. The type that’s so generic, the receiver has no idea what specifically was good or needs improvement.</p>
<p>If you’ve been on a course about giving and receiving feedback in the last 4 years, it’s likely this book was mentioned (or at least the 4 types of feedback). It helped me understand how to give better feedback to others, but also how to receive better feedback through asking for more context.</p>
<h3 id="heading-dichotomy-of-leadershiphttpswwwamazoncoukdichotomy-leadership-balancing-challenges-ownership-ebook"><a target="_blank" href="https://www.amazon.co.uk/Dichotomy-Leadership-Balancing-Challenges-Ownership-ebook">Dichotomy of Leadership</a></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816468402/c45f7456-48fc-4b60-be2b-d2a6bdc1ae8a.jpeg" alt /></p>
<p>After I read Turn the Ship Around, my understanding of servant leadership was to always give people the objective and let them figure it out. This led to some situations where teams or team members would be stuck, but I believed they had to find their way out at all costs.</p>
<p>In reality, leadership is a balance. It’s a balance between knowing when to lead and when to follow. When to give people space to learn, and when the situation requires a solution, fast. This book helped me to explore how I could vary my approach, based on context.</p>
<h3 id="heading-good-leaders-ask-great-questionshttpswwwamazoncoukgood-leaders-ask-great-questions-ebookdpb00npcwlk2"><a target="_blank" href="https://www.amazon.co.uk/Good-Leaders-Ask-Great-Questions-ebook/dp/B00NPCWLK2">Good Leaders Ask Great Questions</a></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816470133/5fd78d9e-01cc-4404-87b7-780a06309223.jpeg" alt="Cover for the book “Good Leaders Ask Great Questions”" /></p>
<p>I quite often had to pause while reading this book to take down a particular quote, that just really cut to the heart of that particular topic so succinctly.</p>
<p>Through reading this book, I realised the importance of being intentional. Making sure I work on the things only I can do, and enable others to do the other things through giving ownership. More than that, though, the book helped me explore how I could be more intentional in how I grow and identify new opportunities for learning, by being deliberate about change.</p>
<p>There’s a lot of great stuff here, from an author that’s spent a lot of time helping people to grow into leadership.</p>
<h3 id="heading-upstreamhttpswwwamazoncoukupstream-solve-problems-before-happen-ebookdpb07vmvz9mn"><a target="_blank" href="https://www.amazon.co.uk/Upstream-solve-problems-before-happen-ebook/dp/B07VMVZ9MN">Upstream</a></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816471868/81ebd27d-6203-452d-a9da-582d53a84f05.jpeg" alt="Cover for the book “Upstream”" /></p>
<p>As a leader, you will over time start to deal with more abstract problems. The answers will be less obvious and require more exploration of the context.</p>
<p>I really enjoyed this book, particularly as the author uses some great examples around education, local government, the NFL and personal finances. They use the topics to cover the barriers they believe people often face in tackling upstream issues — problem blindness (“I don’t see the problem” or “it seems inevitable”), lack of ownership (“that problem isn’t mine to fix”), and tunnelling (“I can’t deal with that right now”).</p>
<p>I’d highly recommend this book for anyone looking to better understand how to get to the root of a problem and implement upstream solutions, over constantly dealing with the downstream effects.</p>
]]></content:encoded></item><item><title><![CDATA[Make ESLint do the Work — Adding Custom Rules]]></title><description><![CDATA[ESLint is a great tool for encouraging best practices within a JS/TS codebase. It takes what sometimes exists just in a document on confluence somewhere into something that can be automatically checked for in every change.
Automating these checks mea...]]></description><link>https://blog.kbremner.com/make-eslint-do-the-work-adding-custom-rules</link><guid isPermaLink="true">https://blog.kbremner.com/make-eslint-do-the-work-adding-custom-rules</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[eslint]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Wed, 27 Oct 2021 08:00:29 GMT</pubDate><content:encoded><![CDATA[<p>ESLint is a great tool for encouraging best practices within a JS/TS codebase. It takes what sometimes exists just in a document on confluence somewhere into something that can be automatically checked for in every change.</p>
<p>Automating these checks means reviewers or collaborators can focus on higher-level questions, instead of comments focussed on pointing at the same issue that’s been repeated many times over or missed in previous commits.</p>
<p>ESLint comes with a great set of <a target="_blank" href="https://eslint.org/docs/rules/">built-in rules</a> and some great community libraries (we’ll cover more on setting this up for a project in a future post), but they have to draw the line somewhere. Some codebases or companies might have more specific needs or preferences. For example, to support migrating away from a particular library method call or avoiding an approach that has known issues.</p>
<p>ESLint can be extended in 2 ways — using “no-restricted” rules or creating a custom plugin.</p>
<h3 id="heading-configuring-no-restricted-rules">Configuring “no-restricted” Rules</h3>
<p>There are 3 built-in rules that we can use to configure custom restrictions:</p>
<ul>
<li><a target="_blank" href="https://eslint.org/docs/rules/no-restricted-syntax">https://eslint.org/docs/rules/no-restricted-syntax</a> — restrict the use of certain “syntax elements” (e.g. method calls).</li>
<li><a target="_blank" href="https://eslint.org/docs/rules/no-restricted-globals">https://eslint.org/docs/rules/no-restricted-globals</a> — restrict the use of certain global variables. This could be useful if there are global variables defined for “hydrating” the app, but that global state should not be accessed directly outside of that use case.</li>
<li><a target="_blank" href="https://eslint.org/docs/rules/no-restricted-properties">https://eslint.org/docs/rules/no-restricted-properties</a> — restrict the use of a named property on a property with a specific name. Useful if we wanted to restrict the use of screen.getByTestId, for example.</li>
</ul>
<p>These rules make it particularly easy to extend ESLint, without the need to implement an actual plugin.</p>
<h4 id="heading-example">Example</h4>
<p>To demonstrate that, let’s run through an example.</p>
<p>While reviewing code changes, we’ve noticed some instances of the following approach:</p>
<pre><code><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">someFunction</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> asyncFunction()
    .then(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> { <span class="hljs-comment">/* do something */</span>});
  <span class="hljs-keyword">return</span> result;
}
</code></pre><p>We’ve had a conversation in the team around why it’s not ideal to mix promise chains with <code>async</code>/<code>await</code> syntax in a single call like this, so we’d rather that one or the other was used in a particular code block. Ideally, though, we’d automate this check so that <code>await</code> and <code>then</code> aren’t used together in future.</p>
<p>We can do this with the <code>no-restricted-syntax</code> rule. First, though, we need to understand what syntax is to be restricted.</p>
<p>Restricted syntax can take an Abstract Syntax Tree (AST) selector. ASTs are a representation of the structure of code and are used by a variety of static analysis tools and code generators. <a target="_blank" href="https://www.twilio.com/blog/abstract-syntax-trees">https://www.twilio.com/blog/abstract-syntax-trees</a> is a decent intro to ASTs if you want to know more. The ESLint docs also have <a target="_blank" href="https://eslint.org/docs/developer-guide/selectors#restricting-syntax-with-selectors">some examples of using AST selectors to restrict syntax</a>.</p>
<p>How do you know what AST selector to use for your use case? Well, the first step I’d suggest is to take the snippet of code that has the syntax to be restricted and copy it into <a target="_blank" href="https://astexplorer.net/.">https://astexplorer.net/.</a> This tool parses and displays the AST for the code that you’ve provided. If you click on a particular method call or parameter, you’ll see that particular node in the tree become highlighted, as shown below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774815808277/3044e021-a6ce-4409-9a21-b10b56a917d0.png" alt="A screenshot from astexplorer.net, showing the part of the tree that represents the call to the" /></p>
<p>By selecting the <code>await</code> keyword and seeing what’s focussed in the AST, we can see that <code>AwaitExpression</code> is the name for the node type we’re looking for. But we don’t want to restrict the use of all <code>await</code> calls, so we need to figure out what properties of that node to use to make our selector more specific.</p>
<p>Similar to <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors">CSS attribute selectors</a>, we can specifically look for <code>AwaitExpression</code> nodes that have a particular nested property value. If we expand the properties of that node, we eventually find what we’re looking for — the “then” property name. If we use the keys to create a path to the “then” keyword, we can pull together the below AST attribute selector:</p>
<pre><code>AwaitExpression[argument.callee.property.name=<span class="hljs-string">"then"</span>]
</code></pre><p>To configure the <code>no-restricted-syntax</code> rule to generate an error using our selector with a more helpful error message, we can add the following to the rules section of our <code>.eslintrc</code> (or wherever else ESLint config is stored):</p>
<pre><code><span class="hljs-string">"no-restricted-syntax"</span>: [
  <span class="hljs-string">"error"</span>,
  {
    <span class="hljs-string">"message"</span>: <span class="hljs-string">"promise.then is unnecessary when using async/await"</span>,
    <span class="hljs-string">"selector"</span>: <span class="hljs-string">"AwaitExpression[argument.callee.property.name=\" then\"]"</span>
  }
]
</code></pre><p><em>(Note the quotes around “then” are escaped).</em></p>
<p>This takes some practice, but this approach can be very powerful when looking to automate checks for best practices and conventions in a codebase. The <a target="_blank" href="https://eslint.org/docs/developer-guide/selectors#restricting-syntax-with-selectors">examples in the ESLint docs on restricting syntax</a> are a good starting point, but ultimately the best way to get started and learn is to give it a shot!</p>
<h3 id="heading-custom-plugin">Custom Plugin</h3>
<p>In some cases, those rules won’t be enough. In those cases, you can take a look at implementing a custom ESLint rule. This still deals with an AST but gives you much more control to create custom logic to implement the desired check.</p>
<p>Check out <a target="_blank" href="https://eslint.org/docs/developer-guide/working-with-plugins#create-a-plugin">https://eslint.org/docs/developer-guide/working-with-plugins#create-a-plugin</a> for info on how to get started, and also <a target="_blank" href="https://dev.to/spukas/how-to-write-your-first-eslint-plugin-145">https://dev.to/spukas/how-to-write-your-first-eslint-plugin-145</a>, which walks through creating a simple plugin.</p>
<p>And that’s it! Next time you see something that you think would be good to automatically check for in a project you’re working on, think about how this could be done via an ESLint rule.</p>
<h3 id="heading-further-reading">Further Reading</h3>
<p><a target="_blank" href="https://www.twilio.com/blog/abstract-syntax-trees">https://www.twilio.com/blog/abstract-syntax-trees</a> <a target="_blank" href="https://eslint.org/docs/developer-guide/selectors">https://eslint.org/docs/developer-guide/selectors</a> <a target="_blank" href="https://dev.to/spukas/how-to-write-your-first-eslint-plugin-145">https://dev.to/spukas/how-to-write-your-first-eslint-plugin-145</a> <a target="_blank" href="https://astexplorer.net/">https://astexplorer.net/</a></p>
]]></content:encoded></item><item><title><![CDATA[3 lessons I learned getting started with Ramda]]></title><description><![CDATA[Ramda is all about those functional pipelines
I did a functional programming course once.
As much as I tried at the time, I couldn’t wrap my head around it. I’d struggle to write anything coherent in Haskell today, I still couldn’t tell you what a mo...]]></description><link>https://blog.kbremner.com/3-lessons-i-learned-getting-started-with-ramda</link><guid isPermaLink="true">https://blog.kbremner.com/3-lessons-i-learned-getting-started-with-ramda</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Functional Programming]]></category><category><![CDATA[Ramda]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Tue, 16 Mar 2021 20:22:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1774815812721/3b4b8515-1572-4ffd-b69a-bfdc9c2eda31.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ramda is all about those functional pipelines</p>
<p>I did a functional programming course once.</p>
<p>As much as I tried at the time, I couldn’t wrap my head around it. I’d struggle to write anything coherent in Haskell today, I still couldn’t tell you what a monad is, or explain the ins and outs of the other mathematical concepts related to functional programming.</p>
<p>With time, though, I have come to really appreciate the approach that functional programming paradigms encourage. The concepts often lead to code that is much easier to reason about, test, compose, and adapt over time.</p>
<p>I have a project that I’ve worked on for years. One of those, “I’ll finish it someday” projects. I often use it to try different approaches and libraries, as it has enough complexity to make it an interesting example and I’m not too concerned about when I finish it. Sometimes, as the saying goes, it’s about the journey more than the destination!</p>
<p>In this post, I’ll go through some lessons that I learned while adding <a target="_blank" href="https://ramdajs.com/">Ramda</a> to that project, to help with taking a more functional approach.</p>
<p>Ramda is a collection of functions that “makes it easy to create functional pipelines”. It’s pretty easy to get started with, but after a while, I did find myself learning a few lessons around how to get the most out of it.</p>
<p>If you’re looking for more of an explainer on functional programming, there’s plenty of other great articles for that. Here’s one example:</p>
<p><strong><a target="_blank" href="https://dev.to/mr_bertoli/an-adequate-introduction-to-functional-programming-1gcl">An Adequate Introduction to Functional Programming</a></strong></p>
<p>This list of libraries and other resources is also worth looking at:</p>
<p><strong><a target="_blank" href="https://github.com/stoeffel/awesome-fp-js">stoeffel/awesome-fp-js</a></strong></p>
<h3 id="heading-leason-1-ramda-function-naming-can-be-confusing">Leason 1 — Ramda function naming can be confusing</h3>
<p>Ramda has a lot of functions, so if you need to do something, there’s probably a function for it.</p>
<p>However, I found it difficult to find what I was looking for because some of the namings seemed a bit non-obvious. For example, I found <a target="_blank" href="https://ramdajs.com/docs/#evolve">R.evolve</a> useful in multiple cases for applying different transforms to each property in an object, but I only found it by chance.</p>
<p>Make sure to <a target="_blank" href="https://ramdajs.com/docs/">explore the docs</a>, you’ll find some interesting tools!</p>
<h3 id="heading-lesson-2-keep-it-simple">Lesson 2 — Keep it simple</h3>
<p>You’ve had a look through the docs and found some interesting methods. All of these methods are great for more complicated logic, but they can also make a relatively simple piece of logic much more difficult to understand.</p>
<p>Let’s take an example — say you want to take the first element from an array, and return that element if it is greater than 0, else return undefined. With Ramda, that could look something like:</p>
<pre><code>R.ifElse(
 R.pipe(R.nth(<span class="hljs-number">0</span>), R.gt(R.__, <span class="hljs-number">0</span>)),
 R.identity,
 R.always(<span class="hljs-literal">undefined</span>)
);
</code></pre><p>On the other hand, an equivalent lambda function could look something like this:</p>
<pre><code>([elem]) =&gt; elem &gt; <span class="hljs-number">0</span> ? elem : <span class="hljs-literal">undefined</span>;
</code></pre><p>While all the methods that Ramda offers can be useful, it’s pretty easy to end up staring at some code you’ve just written, trying to remember what on earth you were even trying to do.</p>
<p>Ramda is there to help write code in a functional way, not to be the only way to write functional code.</p>
<h3 id="heading-lesson-3-need-to-pass-variables-through-to-a-middle-pipe-stage-use-a-lambda">Lesson 3 — Need to pass variables through to a middle pipe stage? Use a lambda!</h3>
<p>Once you start writing more complex pipelines, you’ll eventually reach a point where you have a stage that needs access to a parameter that’s passed in at the start of the pipeline.</p>
<p>We could modify the previous stages to pass through this parameter. However, we’ve now made them aware of knowledge that isn’t important to them (the new parameter) and tied them specifically to this pipeline.</p>
<p>We could instead wrap each method in an adapter:</p>
<pre><code><span class="hljs-keyword">const</span> passThrough = <span class="hljs-function">(<span class="hljs-params">func</span>) =&gt;</span> <span class="hljs-function">(<span class="hljs-params">[arg1, arg2]</span>) =&gt;</span>
  ([func(arg1), arg2]);
</code></pre><pre><code>R.pipe(
  <span class="hljs-function">(<span class="hljs-params">arg1, arg2</span>) =&gt;</span> ([arg1, arg2]),
  passThrough(stage1),
  passThrough(stage2),
  stage3,
  stage4);
</code></pre><p>This adapts the previous stages so that the needs of the later stage can be met, but without having to adjust the reusable stage itself.</p>
<p>A second alternative I found was just to wrap the pipeline in a lambda:</p>
<pre><code>(arg1, arg2) =&gt; R.pipe(
 stage1,
 stage2,
 stage3(arg2),
 stage4,
)(arg1);
</code></pre><p>While this does work, it feels a bit hacky as it introduces top-level state. However, I did found it easier and quicker than the pass-through option above. Which approach makes sense will likely depend on the situation and your own (or your team’s) preferences.</p>
<p>These are as much for my reference as anything else, but I hope these lessons are useful to anyone else getting started with Ramda. If there’s anything you found useful when getting started with Ramda, or if you have questions/thoughts about the above, let me know!</p>
]]></content:encoded></item><item><title><![CDATA[Glasgow Tech Christmas Party 2018 — Behind the Scenes]]></title><description><![CDATA[Another year, another Glasgow Tech Christmas Party! On Friday 19th December 2018, people from across the Glasgow tech community came together to bring to a close another great year.
For those that are interested, this post gives a bit more detail abo...]]></description><link>https://blog.kbremner.com/glasgow-tech-christmas-party-2018-behind-the-scenes</link><guid isPermaLink="true">https://blog.kbremner.com/glasgow-tech-christmas-party-2018-behind-the-scenes</guid><category><![CDATA[community]]></category><category><![CDATA[events]]></category><category><![CDATA[Scotland]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Sun, 27 Jan 2019 09:31:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816453718/df9beb0f-1e38-4858-98c8-f48cf7eed2ba.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Another year, another Glasgow Tech Christmas Party! On Friday 19th December 2018, people from across the Glasgow tech community came together to bring to a close another great year.</p>
<p>For those that are interested, this post gives a bit more detail about what went on behind the scenes to put on the event.</p>
<h3 id="heading-numbers">Numbers</h3>
<p>First off, let’s look at some numbers, compared to 2017’s event.</p>
<h4 id="heading-tickets">Tickets:</h4>
<ul>
<li><strong>70 (-50)</strong> available tickets</li>
<li><strong>46 (-26)</strong> tickets allocated</li>
<li><strong>34 (-22)</strong> people checked in on the night</li>
<li><strong>0 (-7)</strong> tickets allocated on the door</li>
<li><strong>£385</strong> net ticket sales</li>
</ul>
<h4 id="heading-sponsors">Sponsors:</h4>
<ul>
<li><strong>3 (-1)</strong> sponsors (thanks again to <a target="_blank" href="http://streamba.net">Streamba Ltd</a>, <a target="_blank" href="http://www.burnesspaull.com/">Burness Paull</a>, and <a target="_blank" href="https://careerssearch.bbc.co.uk/">BBC</a>!)</li>
<li><strong>£1000 (-£1280)</strong> invoiced sponsorship</li>
<li><strong>£700 (-£1580)</strong> collected sponsorship (more on this below)</li>
</ul>
<h4 id="heading-costs-on-the-night">Costs on the Night:</h4>
<ul>
<li><strong>£515 (-£1570)</strong> cost of food</li>
<li><strong>£600 (-£65)</strong> bar bill</li>
</ul>
<h4 id="heading-total-remaining">Total remaining:</h4>
<p>£700+£385 -£515-£600=<strong>-£30</strong></p>
<h3 id="heading-goal">Goal</h3>
<p>With the Global Day of Coderetreat in November 2018, and a wedding in February 2019, the aim for 2018’s event was simple — put on the Glasgow Tech Christmas Party with as little input as possible.</p>
<h3 id="heading-venue">Venue</h3>
<p>For 2017’s event, the decision was taken to try a bigger venue. With the goal for 2018’s event, the Raven seemed like an easy choice. Central location, great staff, decent food at a good price — what’s not to like?</p>
<p>In previous years, attendees were checked in and given a couple of tokens for getting a drink at the bar. This meant that I spent most of the event doing just that. Looking at this year’s goal, I just set up a bar tab. This made the event much better for me at least, as I got to participate a lot more. Personally, though, the jury’s still out on whether it’s a necessary part of the event.</p>
<h3 id="heading-tickets-1">Tickets</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816455555/1fc35a66-2b72-4296-a955-dafab5b5bd52.png" alt /></p>
<p>Ticket sales over time</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816457315/e4a591d8-b8fc-48a9-a14c-f1e0fd9e5c31.png" alt /></p>
<p>Ticket sales per day</p>
<p>Tickets were made available quite late this year — just a few weeks before the event. In previous years, I’ve put effort in to reaching out to meet ups, universities, trying out twitter ads, etc. While I did tweet a few times, on the whole I didn’t do much to promote this year’s event.</p>
<p>The lack of promotion and the late posting of the event both seem to be the most likely reasons for the reduction in ticket sales this year.</p>
<h3 id="heading-sponsorship">Sponsorship</h3>
<p>The sponsorship tiers offered this year were almost identical to last years.</p>
<p>It was great to have Streamba and Burness Paull support the event for another year, and to have the BBC on board, after they also supported the Glasgow edition of Global Day of Coderetreat 2018.</p>
<p>In previous years I was able to use my business, Defining Technology, to issue invoices. Since joining Skyscanner in 2018 (yay!), that company is no longer active. This presented a new problem, in terms of how to handle sponsorship.</p>
<p>In the end, I opted to issue invoices from myself, which brought about it’s own set of problems. I’m still following up with one organisation, hence the difference between invoiced and collected sponsorship amounts above. If and when that money is received, it’ll be donated to <a target="_blank" href="https://www.mind.org.uk">Mind</a>, a mental health charity.</p>
<p>Overall, the collected sponsorship, with the money from ticket sales, pretty much covered the costs of the event this year.</p>
<h3 id="heading-points-for-next-year">Points for Next Year</h3>
<p>There are a couple of points that I thought about after the event:</p>
<ul>
<li>Promoting the event is important</li>
<li>Bar tab and bar tokens just seem to be a faf — maybe better reducing the ticket price instead?</li>
<li>People seem to be unsure of who the event is aimed at—a few people I talked to thought of it as just for contractors and freelancers.</li>
<li>Not having a business/organisation makes it difficult to collect sponsorship</li>
</ul>
<p>Most of all, though, I’ve been reflecting on having organised a number of events now. Initially, while I tried to organise the event with others, it turned in to herding cats. So, for a while, I’ve ended up just organising events on my own.</p>
<p>While I’ve learnt a lot, running an event on your own has it’s challenges. In the end, it takes up a lot more time and energy than you’d think — 5 mins here and there, chasing sponsors and promoting the event, soon adds up!</p>
<p>While I’ve had plenty of offers of help, I have been rather reluctant because of my previous experiences. One of the most important takeaways from this event (and Global Day of Coderetreat) is that, at least for my own sanity, I want to get better at accepting help.</p>
<p>So, with that in mind, if you’d like to help with 2019’s Glasgow Tech Christmas Party, or indeed any other event I’m organising, please get in touch!</p>
<h3 id="heading-thanks">Thanks</h3>
<p>A big thank you to all that helped with this event, in particular:</p>
<ul>
<li>All the sponsors (<a target="_blank" href="http://streamba.net">Streamba Ltd</a>, <a target="_blank" href="http://www.burnesspaull.com/">Burness Paull</a>, and <a target="_blank" href="https://careerssearch.bbc.co.uk/">BBC</a>)</li>
<li>Michael Hayes, for checking in and asking how things are going, as well as offering advice and support (in 2018 and in previous years)</li>
<li>You! The event is all about bringing people in the community together, so it wouldn’t have been the same without all those that attended</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Glasgow Tech Christmas Party 2017 — Behind the Scenes]]></title><description><![CDATA[Calm before the storm
On Friday 8th December, people from all over Glasgow’s digital community came together to meet friends, old and new, at the end of what has been a great year for the Scottish tech industry. For those that are interested, this po...]]></description><link>https://blog.kbremner.com/glasgow-tech-christmas-party-2017-behind-the-scenes</link><guid isPermaLink="true">https://blog.kbremner.com/glasgow-tech-christmas-party-2017-behind-the-scenes</guid><category><![CDATA[community]]></category><category><![CDATA[events]]></category><category><![CDATA[Scotland]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Wed, 31 Jan 2018 07:31:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816444208/f7411e34-5136-4ceb-accf-0ab4f5ec3978.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Calm before the storm</p>
<p>On Friday 8th December, people from all over Glasgow’s digital community came together to meet friends, old and new, at the end of what has been a great year for the Scottish tech industry. For those that are interested, this post will give a bit more detail about what went on behind the scenes to put on the event.</p>
<h3 id="heading-numbers">Numbers</h3>
<p>First off, lets look at some numbers (compared with 2016’s event):</p>
<h4 id="heading-tickets">Tickets</h4>
<ul>
<li><strong>120 (+40)</strong>— capacity of venue</li>
<li><strong>72 (-8)</strong> tickets allocated</li>
<li><strong>56 (-17)</strong> people checked in on the night</li>
<li><strong>7 (+7)</strong> tickets allocated on the door</li>
</ul>
<h4 id="heading-sponsors">Sponsors</h4>
<ul>
<li><strong>4 (+/- 0)</strong> sponsors (thanks again to <a target="_blank" href="http://streamba.net">Streamba Ltd</a>, <a target="_blank" href="http://www.burnesspaull.com/">Burness Paull</a>, <a target="_blank" href="http://www.bjss.com/">BJSS</a> and <a target="_blank" href="http://cathcartassociates.com">Cathcart Associates</a>!)</li>
<li><strong>£2280 (+£980)</strong> provided by sponsors</li>
</ul>
<h4 id="heading-main-costs">Main Costs</h4>
<ul>
<li><strong>£2085 (+£1595)</strong>— Cost of food</li>
<li><strong>£665 (+£15)</strong>—Bar bill (including welcome drink, 1 voucher + gratuity)</li>
<li><strong>£80 (+£80)</strong> — Twitter ads</li>
</ul>
<p><strong>Remaining funds: -£24.49</strong></p>
<p>A more detailed breakdown of the finances for the event can be found <a target="_blank" href="https://docs.google.com/spreadsheets/d/1JwVVMjOkZ2mOAh8vU2y52JceLHHIi7SVAHzeuL8TcUI/edit?usp=sharing">here</a>.</p>
<h3 id="heading-goal">Goal</h3>
<p>This year, the event set out to achieve one main goal — bring together a wider group of people. While it’s been great to see faces returning each year, it’s always good to see some new faces and hear some other points of view.</p>
<h3 id="heading-venue">Venue</h3>
<p>This year, when looking for a suitable venue, it was decided to make a couple of changes:</p>
<ul>
<li>Look for a venue that was available on a Friday evening, to try and make it easier for more people to attend</li>
<li>Look for a venue that could fit more than 80 people, given the fact that last year’s event sold out a week before</li>
<li>Start looking for a venue much earlier, to avoid the issues faced last year with finding a suitable venue that was still available</li>
</ul>
<p>Unfortunately, this backfired for a number of reasons:</p>
<ul>
<li>Venues are more expensive on Friday evenings</li>
<li>There are much fewer venues that can cater for more than 80 people</li>
<li>Many companies host their own Christmas parties on a Friday evening in December, resulting in the chosen date clashing with a number of other Christmas parties of large companies in Glasgow</li>
</ul>
<p>In the end, Waxy O’Connor’s was chosen due to it’s good central location, high capacity (120 people) and the fact that it is used occasionally by some meetups in Glasgow.</p>
<p>The bar staff were very helpful on the night and the food was pretty good, with the venue catering for all the dietary requirements mentioned by those attending. Despite this, the choice of venue did backfire for a number of reasons:</p>
<ul>
<li>The area booked was not easy to find (could have done a better job at investigating this before hand)</li>
<li>Despite booking the event under “Glasgow Tech Christmas Party”, the venue put up signs saying “Defining Technology Ltd”, which did not help with the first point</li>
<li>The area booked contained the only toilet on that floor, resulting in a lot of through-traffic from other parties</li>
<li>The food for this event was ridiculously expensive, at over 4 times the cost of the food for the previous event</li>
<li>Welcome drinks were paid for, but were put in a place where people didn’t really notice them</li>
<li>While a bigger venue was chosen, due to the chosen Friday clashing with a number of other Christmas parties, the event actually sold less tickets than the previous event</li>
</ul>
<p>Another point worth highlighting was that, despite everyone being given a drinks token, many people did not use them. It is worth considering whether drinks tokens are worth the hassle for any future event.</p>
<h3 id="heading-tickets-1">Tickets</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816445961/c764025d-bc08-48a3-9c6b-cd6a1314426e.png" alt /></p>
<p>Quantity sold over time</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816447715/5812ca40-a47b-421d-a1cc-cebabca062eb.png" alt /></p>
<p>Sales channels (Eventbrite Channels represent complimentary tickets)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816449549/e1d8506c-2cff-4721-8aea-732933ee9f61.png" alt /></p>
<p>Quantity sold per day</p>
<p>For this event, tickets were made available on 13th October, which looking at it now seems ridiculously early. The last event only made tickets available on 2nd November.</p>
<p>While it might seem that having more time would make things easier, what it actually results in is promotion fatigue. Promoting an event is a marathon not a sprint, starting the promotion too early means that there’s no energy to promote in the days leading up to the event, when it is most important. I also started an on-site contract just before the day of the event, which didn’t help with the final push.</p>
<p>Due to a limitation in the time I had available and a want to try and automate the promotion of the event, I decided to try Twitter ads this year. With a budget of £80, the ads were started on the 19th November and ramped up to the day of the event. While this resulted in a total of 7,383 impressions, that only generated 90 clicks, at a cost of £0.89 per click. Your mileage may vary, but this isn’t something I would likely use again for a community event.</p>
<p>On the day of the event, I made a mistake in forgetting to allow eventbrite ticket sales up until after the event. This meant that some people who wanted tickets on the day couldn’t get them. Unsure of why this was the case, a tweet was sent out saying to people that they could pay on the door. This turned out to be a poor choice, as I had no means of providing change. In the end, those that turned up on the night were asked to make a charitable donation, with £20 that was provided on the night being donated to the Social Bite charity.</p>
<h3 id="heading-sponsorship">Sponsorship</h3>
<p>The sponsorship tiers were largely similar to last year’s, except the number of tickets for each tier was reduced and the prices were changed to be exclusive of VAT.</p>
<p>While the event continued to be supported by a number of last year’s sponsors, some did get a custom sponsorship amount as they didn’t see the value in some of the perks of each tier. Saying that, we did have more sponsors opt for higher tiers than for last year’s event.</p>
<p>This event was heavily reliant on sponsorship, due to the much higher venue costs. While the event wouldn’t have been the same without the support of the sponsors, for the next event, one of the main aims should be to focus on simplicity — reduce the costs, reduce the reliance on sponsors.</p>
<h3 id="heading-points-for-next-year">Points for Next Year</h3>
<p>One person on the night summed it up pretty well—it’s better to have 80 people and have 60 of them find it beneficial, than have 120 people and have 70 of them find it beneficial. The event needs to get back to being a simple get together of Glasgow’s tech community at the end of the year, to meet friends, old and new. Losing sight of that is what ultimately resulted in the small loss made by this event.</p>
<p>On that note, here are a few things to be considered before any future event:</p>
<ul>
<li>Stick to mid week (cheaper, less clashes)</li>
<li>Make tickets available 4 weeks before the event — any earlier means there is no energy to promote it in the days running up to the event</li>
<li>Pick a venue with food costs more inline with previous years (if that means restricting numbers to ≤80, fine)</li>
<li>Offer food plus 1 drink — keep it simple (and cheaper)</li>
<li>Stay away from twitter ads</li>
<li>Order less food? Maybe switch to a limited sit down meal, followed by just booking a bar area? Or just book a bar area?</li>
<li>Don’t take tickets on the door (but don’t let ticket sales end until after the event…)</li>
<li>Rethink sponsorship levels (some comments this year about them being a bit high)</li>
</ul>
<h3 id="heading-thanks">Thanks</h3>
<p>A big thank you to all that helped with this event, in particular:</p>
<ul>
<li>Shaun McWhinnie for designing the sponsorship pack this year</li>
<li>You! The event is all about bringing people in the community together, so it wouldn’t have been the same without all those that attended</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Storing ASP.NET Core Data Protection Keys in the Database]]></title><description><![CDATA[If you use Pocket, you will no doubt know the pain of articles stacking up faster than you can read them… The ReadMore chrome extension was developed to help solve this problem by showing you a random article that you have previously saved to Pocket....]]></description><link>https://blog.kbremner.com/storing-aspnet-core-data-protection-keys-in-the-database</link><guid isPermaLink="true">https://blog.kbremner.com/storing-aspnet-core-data-protection-keys-in-the-database</guid><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Fri, 29 Sep 2017 16:03:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1774815772165/529db853-7e4c-4c20-b8dd-60d1a31d6035.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you use <a target="_blank" href="http://getpocket.com">Pocket</a>, you will no doubt know the pain of articles stacking up faster than you can read them… The <a target="_blank" href="https://github.com/defining-technology/readmore-chrome">ReadMore chrome extension</a> was developed to help solve this problem by showing you a random article that you have previously saved to Pocket.</p>
<p>The extension allows users to view an article and then delete or archive that article. The API exposes endpoints for carrying out all of these actions. As the user had specifically given us the ability to modify their articles, one of the considerations was to not leak this privilege. In particular, the API should only be able to delete or archive articles that it had returned in a response.</p>
<h4 id="heading-data-protection-apis">Data Protection APIs</h4>
<p>While looking for ways to solve this problem, I came across the <a target="_blank" href="https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/">ASP.NET Core Data Protection APIs</a>. They provide the ability to easily encrypt and decrypt data, allowing that data to be safely exposed out with the application. Using these APIs allows an article’s Pocket ID to be returned in an encrypted form. When it is received by the endpoints for deleting or archiving an article, the ID is then decrypted and used. This satisfies the requirement that only articles returned by the API can be deleted or archived.</p>
<p>However, there was one issue to overcome — key storage. The Data Protection APIs, by default, handle the auto-generation of keys for carrying out the encryption and decryption. The behaviour of the APIs can be modified by registering implementations of specific interfaces with the service provider. For instance, an implementation of <a target="_blank" href="https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/extensibility/key-management?tabs=aspnetcore2x#ixmlrepository">IXmlRepository</a> can be registered to change how keys are stored.</p>
<p>The Data Protection APIs come with <a target="_blank" href="https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/implementation/key-storage-providers#data-protection-implementation-key-storage-providers">several implementations of the IXmlRepository interface</a>. Unfortunately, as the API for the extension is hosted on Heroku, none of these options were particularly useful. Ideally, I wanted to store the keys in the database. So, I set out to create a custom IXmlRepository implementation.</p>
<h4 id="heading-implementing-ixmlrepository">Implementing IXmlRepository</h4>
<p>The actual implementation used in the API for the extension can be found <a target="_blank" href="https://github.com/defining-technology/read-more-api/blob/master/ReadMoreData/DbXmlKeysRepository.cs">here</a>, but for this post I’ve cut things down a bit. The IXmlRepository interface consists of two methods:</p>
<ul>
<li><strong>GetAllElements()</strong>— responsible for returning a collection containing all the stored elements</li>
<li><strong>StoreElement(XElement element, string friendlyName)</strong> — handles the storing of an element</li>
</ul>
<p>To store the keys, we just need a simple table to hold the XML for each key:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-string">"XmlKeys"</span>(
   <span class="hljs-keyword">Id</span> <span class="hljs-keyword">UUID</span> PRIMARY <span class="hljs-keyword">KEY</span>     <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">default</span> gen_random_uuid(),
   <span class="hljs-keyword">Xml</span>             <span class="hljs-built_in">TEXT</span>    <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>
);
</code></pre>
<p>Based on that table, we can implement IXmlRepository:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Based on the implementation in https://github.com/defining-technology/read-more-api.</span>
<span class="hljs-comment">// Dapper is used to help with executing SQL queries, but you could use any</span>
<span class="hljs-comment">// abstraction over IDbConnection that you prefer.</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SqlXmlRepository</span> : <span class="hljs-title">IXmlRepository</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> TableName = <span class="hljs-string">"\"XmlKeys\""</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> Func&lt;IDbConnection&gt; _connectionFactory;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">SqlXmlRepository</span>(<span class="hljs-params">Func&lt;IDbConnection&gt; connectionFactory</span>)</span>
    {
        _connectionFactory = connectionFactory;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> IReadOnlyCollection&lt;XElement&gt; <span class="hljs-title">GetAllElements</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> conn = Connection)
        {
            <span class="hljs-keyword">return</span> conn.Query&lt;XmlKey&gt;(<span class="hljs-string">$"select * from <span class="hljs-subst">{TableName}</span>"</span>)
                .Select(x =&gt; XElement.Parse(x.Xml))
                .ToList();
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">StoreElement</span>(<span class="hljs-params">XElement element, <span class="hljs-keyword">string</span> friendlyName</span>)</span>
    {
        <span class="hljs-keyword">var</span> key = <span class="hljs-keyword">new</span> XmlKey
        {
            Xml = element.ToString(SaveOptions.DisableFormatting)
        };
        <span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> conn = Connection)
        {
            <span class="hljs-keyword">return</span> conn.ExecuteScalar&lt;Guid&gt;(<span class="hljs-string">$"INSERT into <span class="hljs-subst">{TableName}</span>(Xml) values (@Xml) returning Id"</span>, key);
        }
    }

    <span class="hljs-keyword">private</span> IDbConnection Connection
    {
        <span class="hljs-keyword">get</span>
        {
            <span class="hljs-keyword">var</span> conn = _connectionFactory();
            conn.Open();
            <span class="hljs-keyword">return</span> conn;
        }
    }
}
</code></pre>
<p>Overall, there’s not too much to it — it uses an IDbConnection instance to execute INSERT and SELECT queries, while also handling converting the XML to a string and back again.</p>
<h4 id="heading-run-migrations-before-creating-keys">Run Migrations Before Creating Keys</h4>
<p>The move from ASP.NET Core 1.x to 2.x isn’t all plain sailing. In 1.x, an initial key was generated when it was first needed. In 2.x, this was changed to generate that initial key when the application started. The outcome of this change was that the tables did not exist when this initial key was created, as the migration scripts had not yet been run.</p>
<p>After searching around the implementation for the Data Protection APIs (thank goodness for open source!), I found out that an implementation of IStartupFilter was added by the Data Protection APIs to handle this key creation. This explained why the key was being created before the migrations were being run — all registered IStartupFilter instances are processed prior to Startup.Configure being called, which was the method handling the execution of the migration scripts (special thanks goes to <a target="_blank" href="https://andrewlock.net/exploring-istartupfilter-in-asp-net-core/">this great article on IStartupFilters</a>).</p>
<p>The answer was to implement an IStartupFilter instance to handle the migrations (the actual implementation used can be found <a target="_blank" href="https://github.com/defining-technology/read-more-api/blob/master/ReadMoreAPI/DbMigrationsStartupFilter.cs">here</a>).</p>
<h4 id="heading-wiring-it-all-together">Wiring it All Together</h4>
<p>The last step in all this is to register the instances.</p>
<p>First off, we register our instance of IXmlRepository. Once we’ve done that, we register our IStartupFilter instance so that it executes before the IStartupFilter registered by the Data Protection APIs. Finally, we can call AddDataProtection() to enable Data Protection.</p>
<pre><code class="lang-csharp">
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Startup</span>
{
    <span class="hljs-comment">// ...</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ConfigureServices</span>(<span class="hljs-params">IServiceCollection services</span>)</span>
    {
        services.AddMvc();

        <span class="hljs-comment">// ...</span>

        <span class="hljs-comment">// this part expects that you've already registered an IDbConnection implemention</span>
        services.AddSingleton&lt;Func&lt;IDbConnection&gt;&gt;(provider =&gt; provider.GetService&lt;IDbConnection&gt;);
        services.AddSingleton&lt;IXmlRepository, SqlXmlRepository&gt;();

        <span class="hljs-comment">// Add a startup filter to handle migrations here, so that the required</span>
        <span class="hljs-comment">// tables are present before the KeyManager attempts to retrieve data</span>
        <span class="hljs-comment">// protection keys via the SqlXmlRepository</span>
        services.TryAddEnumerable(ServiceDescriptor.Singleton&lt;IStartupFilter, DbMigrationsStartupFilter&gt;());

        <span class="hljs-keyword">var</span> sp = services.BuildServiceProvider();
        services.AddDataProtection()
            .AddKeyManagementOptions(options =&gt; options.XmlRepository = sp.GetService&lt;IXmlRepository&gt;());
    }

    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Unfortunately there’s a bit of a quirk between the ASP.NET Core 1.x and 2.x implementations of the Data Protection APIs. According to <a target="_blank" href="https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/extensibility/key-management?tabs=aspnetcore2x#ixmlrepository">the docs</a>, registering an implementation should be all that is required to override the default IXmlRepository implementation:</p>
<blockquote>
<p>To change the default repository application-wide, register a custom singleton IXmlRepository in the service provider</p>
</blockquote>
<p>While this worked in the 1.x version, it does not work in the 2.x version. So, at least for now, we need to add the line found after the call to AddDataProtection above, to explicitly set the XmlRepository instance to be used.</p>
<h4 id="heading-wrap-up">Wrap Up</h4>
<p>I hope this helps anyone else looking at how to store Data Protection keys in a database, if you’ve got any questions or comments, just respond to this article or <a target="_blank" href="http://twitter.com/_kbremner">drop me a tweet</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Glasgow Tech Christmas Party 2016 — Behind the Scenes]]></title><description><![CDATA[Calm before the storm
On Wednesday 7th December, people from all over Glasgow’s digital community came together to meet friends, old and new, at the end of what has been a great year for the Scottish tech industry. For those that are interested, this...]]></description><link>https://blog.kbremner.com/glasgow-tech-christmas-party-2016-behind-the-scenes</link><guid isPermaLink="true">https://blog.kbremner.com/glasgow-tech-christmas-party-2016-behind-the-scenes</guid><category><![CDATA[community]]></category><category><![CDATA[events]]></category><category><![CDATA[Scotland]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Fri, 30 Dec 2016 17:47:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816436612/2a2cdefa-2d32-422a-af17-b637a099d6da.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Calm before the storm</p>
<p>On Wednesday 7th December, people from all over Glasgow’s digital community came together to meet friends, old and new, at the end of what has been a great year for the Scottish tech industry. For those that are interested, this post will give a bit more detail about what went on behind the scenes to put on the event.</p>
<h3 id="heading-numbers">Numbers</h3>
<p>First off, lets look at some numbers:</p>
<h4 id="heading-tickets">Tickets</h4>
<ul>
<li><strong>80</strong> — capacity of venue</li>
<li><strong>80</strong> tickets allocated</li>
<li><strong>73</strong> people checked in on the night</li>
<li><strong>3rd December</strong>— date that the original batch of 70 tickets sold out</li>
</ul>
<h4 id="heading-sponsors">Sponsors</h4>
<ul>
<li><strong>4</strong> sponsors (thanks again to <a target="_blank" href="http://streamba.net">Streamba Ltd</a>, <a target="_blank" href="http://www.grantthornton.co.uk/">Grant Thornton</a>, <a target="_blank" href="http://www.burnesspaull.com/">Burness Paull</a> and <a target="_blank" href="http://cathcartassociates.com">Cathcart Associates</a>!)</li>
<li><strong>£1300</strong> provided by sponsors</li>
</ul>
<h4 id="heading-venue-costs">Venue Costs</h4>
<ul>
<li><strong>£490</strong> — Cost of food</li>
<li><strong>£650</strong>— Drinks vouchers used</li>
</ul>
<h4 id="heading-donations-to-the-mental-health-foundation">Donations to The Mental Health Foundation</h4>
<ul>
<li><strong>£391.36</strong> — Remaining funds</li>
<li><strong>£45</strong> — Funds collected on the night</li>
</ul>
<p>A more detailed breakdown of the finances for the event can be found <a target="_blank" href="https://drive.google.com/file/d/0B0Pf42NKqpzfSEI5ZzUxeE1oVU0/view?usp=sharing">here</a>.</p>
<h3 id="heading-venue">Venue</h3>
<p>For the last couple of years the event has been at The Raven, home to many meetups within the Glasgow tech community. In previous years (and this year), they have been very accommodating and helpful in the run up to and on the night of the event.</p>
<p>I started talking to others around the 13th October about the possibility of organising the Glasgow Tech Christmas Party. We set our sights on organising the event on a Friday or Saturday evening this year, so as to make it easier for people to attend without worrying about sore heads at work the next morning. We also wanted to investigate options at venues other than The Raven.</p>
<p>As it turns out, 13th October is actually quite late to be talking to venues about a Christmas party for 70–80 people... Of the venues that we spoke to, most didn’t have any weekend availability in December or charged a premium for it. Also, food at many of the venues was much more than the price offered by The Raven.</p>
<p>Given that there was no upfront cost, it was known by many who attended meetups held there and the cost of food could be covered by money from ticket sales if necessary, The Raven was chosen as the best venue option.</p>
<h3 id="heading-tickets-1">Tickets</h3>
<p>When organising an event, one of the most painful aspects for me is ticket sales. Will people buy tickets? Do people know about the event? Will they be put off by the cost of a ticket?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816438412/fe248814-1154-4903-9840-e854ed39648b.png" alt /></p>
<p>Gross ticket sales over time</p>
<p>It’s probably not surprising that, as the graph above shows, ticket sales started off pretty slow — there are often people who know straight away that they can attend an event and so buy their ticket early, but most will tend to wait, often until the last minute.</p>
<p>Ticket sales were rather sporadic in the weeks before the event, as can be seen below. It was a welcome surprise when the first 70 tickets sold out on 3rd December and when the last 10 tickets were allocated to those on the waiting list before the event started.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816440178/d81147dc-4b46-44f0-97aa-05dce1e334fe.png" alt /></p>
<p>Gross ticket sales per day</p>
<p>There were a few tactics used to encourage ticket sales. Myself and others in the community tweeted almost every other day about the event in the month running up to it.</p>
<p>An email was also sent out to previous attendees, which only resulted in 2 ticket sales. So, probably not one of the best channels to use for future events.</p>
<h3 id="heading-sponsorship">Sponsorship</h3>
<p>In previous years, those attending the event have paid £10 (+ Eventbrite fees) for a ticket and then been given back some or all of their ticket price in drink vouchers on the night.</p>
<p>We decided to do the same again this year, which meant raising sponsorship to cover all the other costs associated with the event (decorations, food, etc).</p>
<p>A <a target="_blank" href="https://drive.google.com/file/d/0B0Pf42NKqpzfZGE2NVRWWU9MQ1k/view?usp=sharing">sponsorship pack</a> was created with a number of sponsorship levels, including tickets to attend the event and also perks like being able to put out promotional material and having their company logo shown on the drink vouchers. A blog post was also released a week before the event to let attendees know more about the sponsors and to help encourage ticket sales.</p>
<p>After cold emailing many companies involved in the tech community in Glasgow and making little progress, it is largely thanks to introductions made by <a target="_blank" href="https://twitter.com/_MDHayes">Michael Hayes</a> of <a target="_blank" href="https://addjam.com/">AddJam</a> that we managed to get sponsorship from <a target="_blank" href="http://streamba.net">Streamba Ltd</a>, <a target="_blank" href="http://www.grantthornton.co.uk/">Grant Thornton</a>, <a target="_blank" href="http://www.burnesspaull.com/">Burness Paull</a> and <a target="_blank" href="http://cathcartassociates.com">Cathcart Associates</a> (thanks again!).</p>
<p>Thanks to our sponsors, we were able to cover all the costs of the event and still donate a total of <strong>£436.36</strong> to <a target="_blank" href="http://mentalhealth.org.uk">The Mental Health Foundation</a>.</p>
<h3 id="heading-points-for-next-year">Points for Next Year</h3>
<p>While the event was largely a success, there’s a few things I’d do differently if organising the event next year:</p>
<ul>
<li><strong>Drink vouchers</strong> — Those attending the event this year generally seemed to feel that £10 for a community event, with a glass of wine and buffet, was good value. I’d probably reconsider giving drink vouchers out at any future event.</li>
<li><strong>Venue</strong> — While The Raven were very accommodating, they became the limiting factor in the number of tickets we could sell. I’d suggest talking to other, larger venues before October, to try and get a Friday or Saturday booking.</li>
<li><strong>Promotion</strong> — While the event was largely well attended by people from a range of companies, there was noticeably little turn out from some of the larger companies in the Glasgow digital sector (MadeBrave, SkyScanner, JP Morgan, etc). In future, I’d look at trying to get someone within these organisations to spread the word about the event, encouraging people to attend and engage with others in the same industry as them.</li>
<li><strong>Sponsorship Levels</strong> — Levels above the basic sponsorship level provided far too many tickets. In future I would cut this back to a maximum of 5 tickets for a sponsorship level.</li>
<li><strong>Summer Event</strong> — It seems like once a year is a bit infrequent, maybe it’s worth having a summer event to bring the community together?</li>
</ul>
<h3 id="heading-thanks">Thanks</h3>
<p>The Glasgow Tech Christmas Party 2016 was a huge success, but there are a few people I’d like to thank for their support in organising the event:</p>
<ul>
<li><a target="_blank" href="https://twitter.com/_MDHayes">Michael Hayes</a> was always asking how things were going in the month before the event, offering advice and helping with introductions with potential sponsors</li>
<li><a target="_blank" href="https://twitter.com/aaronbassett">Aaron Bassett</a> helped with putting together the sponsorship pack and looking at alternative venues</li>
<li><a target="_blank" href="https://twitter.com/zkomives">Zoltan Komives</a> helped with deciding on the sponsorship levels and on the night with checking people in as they arrived</li>
<li>You! The event is all about bringing people in the community together, so it wouldn’t have been the same without all those that attended</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Glasgow Tech Christmas Party 2016!]]></title><description><![CDATA[Dig out that Christmas jumper, dust off your Santa hat and get ready for some seasonal cheer… It’s the Glasgow Tech Christmas Party!
Back for the 3rd year running, the event brings together developers and designers, creatives and disruptors, and all ...]]></description><link>https://blog.kbremner.com/glasgow-tech-christmas-party-2016</link><guid isPermaLink="true">https://blog.kbremner.com/glasgow-tech-christmas-party-2016</guid><category><![CDATA[community]]></category><category><![CDATA[events]]></category><category><![CDATA[Scotland]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Wed, 30 Nov 2016 18:37:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816424706/c39c8de5-b3c2-4666-b7fa-f94a16403436.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dig out that Christmas jumper, dust off your Santa hat and get ready for some seasonal cheer… <strong>It’s the Glasgow Tech Christmas Party</strong>!</p>
<p>Back for the 3rd year running, the event brings together developers and designers, creatives and disruptors, and all the other wonderful people that help make the tech community in Glasgow what it is.</p>
<p>Come along to <strong>The Raven</strong> on <strong>Wednesday 7th December</strong> for some holiday spirit, delicious bites, good company and general merriment.</p>
<p>Tickets are just <strong><a target="_blank" href="https://gla-xmas-tech.eventbrite.com/?aff=medium">£10 + eventbrite fees</a></strong>.</p>
<blockquote>
<p>As someone who has been to the last two Tech Christmas parties I have to say it is a real highlight of the year, I always meet people that I have not seen in ages, and new interesting people I would not have met otherwise. You are SERIOUSLY missing out if you don’t go. Buy a ticket!</p>
<ul>
<li>Kevin Brolly, <a target="_blank" href="http://www.thisisbraw.co.uk/">Braw Software</a></li>
</ul>
</blockquote>
<p>The event is completely volunteer-organised and the ticket money goes towards the cost of the venue, food, etc. Anything left at the end of the night will be donated to this year’s chosen charity, <a target="_blank" href="https://www.mentalhealth.org.uk/">the Mental Health Foundation</a>.</p>
<p>We also want to say a huge thank you to all of this year’s sponsors, who help us to put on this great event!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816426769/24a30710-bbb1-403a-a9ee-9c9cdb6d1cb9.png" alt /></p>
<p><a target="_blank" href="http://streamba.net">Streamba</a> are building the “Google” of the energy supply chain. <a target="_blank" href="http://www.streamba.net/vor/">VOR</a> is the single global source of operational truth for it’s customers, giving them complete visibility over their supply chain. Currently <a target="_blank" href="http://www.streamba.net/careers/">hiring</a> for their Glasgow office, they are keen to support and grow the Glasgow tech community, using the success and recent exits of companies in Edinburgh, like FanDuel and SkyScanner, as a benchmark and inspiration.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816428646/3c20258e-46fe-424b-ba72-1f3c10bd3325.jpeg" alt /></p>
<p><a target="_blank" href="http://www.burnesspaull.com/">Burness Paull</a> is a premier independent commercial law firm with offices in Aberdeen, Edinburgh and Glasgow. The 59 partner firm advises clients in Scotland, the UK and internationally. In the last year they have represented clients and project managed over £100bn of deals in more than 60 jurisdicutions. Burness Paull’s key focus is on Corporate Scotland and the technology, property &amp; infrastructure, financial and oil &amp; gas sectors. Bringing together experienced tech-aware practitioners from every discipline, the team looks to understand how tech drives your business and can deliver solutions that work for you.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816430325/d0e84319-b87d-4414-94da-92e7428020c2.jpeg" alt /></p>
<p><a target="_blank" href="http://www.grantthornton.co.uk">Grant Thornton</a> have chosen to set their reputation alongside a bold purpose — by unlocking the potential for growth in their people, clients and their communities they believe they can help shape a vibrant economy where businesses and people can flourish. They believe that today’s world is all about connecting across businesses and industries, between public and private. And they have a track record of making that happen. They provide advice to more than 40,000 dynamic organisations across the public and private sector, and working with those organisations excites them because they know they help a vibrant economy to thrive.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816432010/89e4ab21-1323-4b98-8a31-392de1de2101.png" alt /></p>
<p><a target="_blank" href="http://cathcartassociates.com">Cathcart Associates</a> is Scotland’s leading independent IT Recruitment Consultancy. They focus solely on Technology, and have dedicated Consultants who each specialise in a niche technical and geographic area. They truly understand your skills and experience, and so are best placed to help you find your next job/hire. They have a strong presence in Glasgow, and love to get involved in tech meetups and conferences (and parties!) — as well as their everyday business of helping good people find the right job and helping companies grow with fantastic technical talent.</p>
]]></content:encoded></item><item><title><![CDATA[Weekly Roundup (Week 36, 2016)]]></title><description><![CDATA[Thoughts This Week
Not much to report this week. I had the chance to take it easy this weekend, for a change. So easy, in fact, that I forgot to publish this blogpost… So, on to the picks!
Podcast Picks
218 FS Don't Agree to Project Deadlines - The F...]]></description><link>https://blog.kbremner.com/weekly-roundup-week-36-2016</link><guid isPermaLink="true">https://blog.kbremner.com/weekly-roundup-week-36-2016</guid><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Mon, 12 Sep 2016 20:57:59 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-thoughts-this-week">Thoughts This Week</h3>
<p>Not much to report this week. I had the chance to take it easy this weekend, for a change. So easy, in fact, that I forgot to publish this blogpost… So, on to the picks!</p>
<h3 id="heading-podcast-picks">Podcast Picks</h3>
<p><strong><a target="_blank" href="http://pca.st/XbB7">218 FS Don't Agree to Project Deadlines - The Freelancers' Show</a></strong></p>
<p>I imagine that most people reading this have been in the position where they’ve felt forced to agree to unrealistic deadlines, either at work or with a client. This episode gives some insights in to why people agree to these deadlines, why they are bad and what you can do to avoid agreeing to them in the first place.</p>
<p><strong><a target="_blank" href="http://pca.st/i1TL">Slack's Architecture with Keith Adams - Podcast - Software Engineering Daily</a></strong></p>
<p>With many companies using it for communication between team members, Slack have had to make some big decisions about their architecture as they’ve grown. This episode has the lead architect from Slack talking about how they’ve made those decisions and what they’re having to do going forward to stay ahead.</p>
<h3 id="heading-article-picks">Article Picks</h3>
<blockquote>
<p><em><a target="_blank" href="http://iamashley.co.uk/2016/09/06/launch-week/">http://iamashley.co.uk/2016/09/06/launch-week/</a></em></p>
</blockquote>
<p>This post comes from <a target="_blank" href="https://rookieoven.com">RookieOven</a> neighbour, <a target="_blank" href="https://withjack.co.uk/">Jack</a>, who recently launched (congrats!). They provide simple, quality insurance for freelancers, with a focus on good design and a friendly approach (just check out their <a target="_blank" href="https://withjack.co.uk/quote/">quote page</a>… See what I mean?). In this article, Ashley, the founder of Jack, talks about what she has learnt from launching Jack, providing some interesting insight in to what did and didn’t work well.</p>
<p><strong><a target="_blank" href="https://www.fastcompany.com/3063392/your-most-productive-self/how-writing-to-do-lists-helps-your-brain-even-when-you-dont-comple">How Writing To-Do Lists Helps Your Brain (Whether Or Not You Finish Them)</a></strong></p>
<p>I use todo lists quite a lot, especially when I’ve got a lot on. Taking the time to make tasks concrete enough to go on a post-it note or on a piece of paper makes a big difference to me. In this article, the author talks about some of the benefits of writing todo lists and why they work.</p>
<p>That’s it for this week’s roundup! Got a problem you think I could help solve? Let’s <a target="_blank" href="http://defining.tech/about">grab a coffee</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Weekly Roundup (Week 35, 2016)]]></title><description><![CDATA[Thoughts This Week

Got a good fire going up in Crianlarich this weekend
This week I’ve felt very busy. Not productive, just busy. The difference, you ask? Well, there’s quite a lot of articles on that topic.
Being productive is about understanding t...]]></description><link>https://blog.kbremner.com/weekly-roundup-week-35-2016</link><guid isPermaLink="true">https://blog.kbremner.com/weekly-roundup-week-35-2016</guid><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Sun, 04 Sep 2016 12:03:30 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-thoughts-this-week">Thoughts This Week</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774815749203/5677f36d-2607-4dfc-a4ff-06960a0bc4bb.jpeg" alt /></p>
<p>Got a good fire going up in Crianlarich this weekend</p>
<p>This week I’ve felt very busy. Not productive, just busy. The difference, you ask? Well, there’s quite <a target="_blank" href="https://www.google.co.uk/search?q=productive+vs+busy">a lot of articles on that topic</a>.</p>
<p>Being productive is about understanding that there is only a certain amount of time to complete the tasks that you have. You make a conscious decision to prioritise the tasks. To understand what you can and can’t get done in a given week.</p>
<p>Being busy, on the other hand, is about trying to make more time to complete all the tasks that you have. You try to work as much as possible. You feel guilty when you’re not working. At the end of it all, the list of tasks is still there and you feel defeated for not having completed them all. You then go back to the start and try to fit more tasks in to the next week, repeating this cycle until burnout hits you like a ton of bricks.</p>
<p>This weekend I went north with Eilidh to Crianlarich for a chance to take a break from everything. It’s all too easy to forget to take these breaks. When you do, though, you remember just why they are so important.</p>
<h3 id="heading-podcast-picks">Podcast Picks</h3>
<p><strong><a target="_blank" href="http://pca.st/Zo9s">Elixir Fountain Ben Marx 2016-08-16 - Elixir Fountain</a></strong></p>
<p>Elixir Fountain is a great show for anyone with an interest in <a target="_blank" href="http://elixir-lang.org/">Elixir</a>. In this episode, the host talks with Ben Marx from <a target="_blank" href="http://bleacherreport.com/">Bleacher Report</a> about their experience running Elixir in production since v1.0.</p>
<p><strong><a target="_blank" href="http://pca.st/k5Ik">30 Consulting, Freelance, and Side Projects - React Native Radio</a></strong></p>
<p>Although this episode is from the React Native Radio podcast, in it the host and guests discuss their experiences doing contract and freelance software development, both full time and part time. It has some interesting insights about how each of them approach common problems such as pricing and managing time.</p>
<h3 id="heading-article-picks">Article Picks</h3>
<p><strong><a target="_blank" href="https://shift.newco.co/how-a-single-conversation-with-my-boss-changed-my-view-on-delegation-and-failure-ae5376451c8d">How a single conversation with my boss changed my view on delegation and failure</a></strong></p>
<p>In this article, the author discusses the problem of workplaces that encourage the silencing of failure. They reason about why, ultimately, it is healthier for failure to be recognised as an opportunity for learning and for the organisation to move forward.</p>
<p><strong><a target="_blank" href="http://www.upworthy.com/you-may-suffer-from-impostor-syndrome-lots-of-smart-people-with-signs-of-high-achievement-do">You may suffer from 'impostor syndrome.' Lots of smart people with signs of high achievement do.</a></strong></p>
<p>This. 100 times, this. As I alluded to in last week’s post, sometimes I can struggle with believing the value that I can provide. Sometimes you‘ve just got to have the confidence to look at what you do and realise that it is obviously of value to someone, or they wouldn’t be paying you to do it.</p>
<p>That’s it for this week’s roundup! Got a problem you think I could help solve? Let’s <a target="_blank" href="http://defining.tech/about">grab a coffee</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Weekly Roundup (Week 34, 2016)]]></title><description><![CDATA[For a while, I’ve wanted to share what I get up to in a given week. What podcasts have I been listening to? What articles have I been reading?
Starting with this post, I’ll be sharing all of those things, on a weekly basis, helping others to discover...]]></description><link>https://blog.kbremner.com/weekly-roundup-week-34-2016</link><guid isPermaLink="true">https://blog.kbremner.com/weekly-roundup-week-34-2016</guid><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Sat, 27 Aug 2016 13:49:52 GMT</pubDate><content:encoded><![CDATA[<p>For a while, I’ve wanted to share what I get up to in a given week. What podcasts have I been listening to? What articles have I been reading?</p>
<p>Starting with this post, I’ll be sharing all of those things, on a weekly basis, helping others to discover interesting content and find out what I’m actually spending my time on!</p>
<h3 id="heading-thoughts-this-week">Thoughts This Week</h3>
<p>I’m coming to the end of a contract I’ve been working on for the past couple of months and so have been thinking about where the next piece of work is going to come from.</p>
<p>I’ve been fortunate enough to have gained experience across many different industries, from transport, to audio, to Oil &amp; Gas. I’ve used various technologies, from Java to develop native Android apps, to C# for ASP.NET MVC backends, to React for an embeddable website widget. While this has been great, it puts me in a somewhat awkward position when trying to sell that experience.</p>
<p>I often get caught up in the fact that I don’t have 5 years experience in one particular area. I often find it difficult to backup my claims that I’m a good JavaScript/C# developer.</p>
<p>Lately, I’ve been thinking a lot about how to make that easier. How to get some confidence about what I’m selling. In the end, I felt that it came down to having some evidence. Having something I could point to and say, “take a look at that, that’s why I’m a good JavaScript developer”.</p>
<p>So, this week, I came up with a list of projects that I will be focussing on. Each of them will be open-sourced and will be accompanied by blog posts about their design and any other interesting points. One of those projects is almost ready and I’ll hopefully be able to say more about it next week!</p>
<h3 id="heading-podcast-picks">Podcast Picks</h3>
<p><strong><a target="_blank" href="http://pca.st/hpxZ">Bootstrapped Web | For Entrepreneurs Bootstrapping Web Startups | Interviews &amp; Business Case…</a></strong></p>
<p>When you’re thinking of creating a new SAAS product, pricing can be a tricky thing to get right. Too low and development will be slow due to lack of capital, too high and potential customers may not see it as worth it for the potential value.</p>
<p>The hosts of Bootstrapped Web, along with a guest, talk about their experience with trying to price SAAS products.</p>
<p><strong><a target="_blank" href="http://pca.st/o5FINk">Startups For the Rest of Us</a></strong></p>
<p><a target="_blank" href="https://nugget.one/about/">Nugget</a> is an interesting concept — charge people for receiving startup ideas in their inbox, every day. Each idea has come from a real potential customer and includes some basic validation and suggestions for next steps (i.e. potential competitors that you should look at).</p>
<p>In this episode, the hosts of Startups for the Rest of Us are joined by the creator of Nugget to discuss where the concept came from, how they released it and how they settled on pricing for it.</p>
<h3 id="heading-article-picks">Article Picks</h3>
<p><strong><a target="_blank" href="http://ferd.ca/queues-don-t-fix-overload.html">Queues Don't Fix Overload</a></strong></p>
<p>This article was recommended during this month’s <a target="_blank" href="https://opentechcalendar.co.uk/event/3784-techmeetup-glasgow">TechMeetup Glasgow</a> talk on microservices, by Kevin McDermott.</p>
<p>The author talks about the need to implement hard operational limits when designing distributed systems. Allowing services to reject data (back-pressure) or drop data (load-shedding) when they reach those limits pushes the issue back to the data source, allowing the data source to deal with the issue appropriately (i.e. retry).</p>
<p>Without these limits, the sources of data believe that everything is okay, the amount of in-flight data increases until it causes issues in some other area, which is then difficult to track back to the service that was at capacity and so caused the issue.</p>
<p>The author talks about the general approaches many people suggest to solving these problems and why they are just displacing the issues rather than solving them.</p>
<p>That’s it for this week’s roundup! Got a problem you think I could help solve? Let’s <a target="_blank" href="http://defining.tech/about">grab a coffee</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Defining Technology, 3 months on…]]></title><description><![CDATA[It’s been a few months now since I quit my job to start Defining Technology. It’s been quite an experience, so this post is a chance to share some of what I’ve learnt and what I’m doing going forward.
1. Square business cards don’t fit in a wallet…

...]]></description><link>https://blog.kbremner.com/defining-technology-3-months-on</link><guid isPermaLink="true">https://blog.kbremner.com/defining-technology-3-months-on</guid><category><![CDATA[startup]]></category><category><![CDATA[Entrepreneurship]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Sat, 06 Aug 2016 13:06:59 GMT</pubDate><content:encoded><![CDATA[<p>It’s been a few months now since I quit my job to start <a target="_blank" href="http://defining.tech">Defining Technology</a>. It’s been quite an experience, so this post is a chance to share some of what I’ve learnt and what I’m doing going forward.</p>
<h3 id="heading-1-square-business-cards-dont-fit-in-a-wallet">1. Square business cards don’t fit in a wallet…</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774815742158/d5b25cb1-7569-4e55-a2e9-ed7d510aa8b2.jpeg" alt /></p>
<p>Some people hate business cards, but I like having something I can give to someone new that I meet that gives them some means of remembering me.</p>
<p>After building the website for the company, I thought that square business cards would be a good fit with the logo. While they were well received, I quickly realised a slight design flaw — they don’t fit in a standard wallet. This does mean that they don’t get lost in the back of someone’s wallet, but they can get lost somewhere else just as easily, like a jacket pocket. I’m still in two minds about this one, but for the moment at least, they’re here to stay.</p>
<h3 id="heading-2-grow-and-utilise-your-network">2. Grow and utilise your network</h3>
<p>Getting your first few jobs can be a daunting prospect, so to be able to talk to as many people as possible, just to say “hey, I’m here and happy to help”, can make that task a lot easier. It certainly has for me.</p>
<p>Renting a desk at <a target="_blank" href="http://rookieoven.com/coworking/">RookieOven</a> has also proved useful, giving me an opportunity to network and get advice from others at various stages of growing their company.</p>
<p>I’ve not being going to that many events in Glasgow and Edinburgh in recent months. I’ll be trying to pick that back up so that I can meet new people and find out what others are doing in the software development world.</p>
<h3 id="heading-3-have-a-clear-set-of-goals">3. Have a clear set of goals</h3>
<p>I get a sinking feeling whenever someone asks me, “so what do you do?”. My business currently sells both my experience in software development on a contract basis and my ability to leverage that experience to complete freelance projects. But that only describes what my business does, today. It doesn’t convey my vision for where I want the company to go. To create products that make people’s lives easier.</p>
<p>Having a clear set of goals for what you want you and your company to achieve makes it easier to figure out the tasks required to achieve them. In the coming weeks, I will be sitting down and figuring out my goals for the next 6 months to a year and the tasks required to achieve them.</p>
<h3 id="heading-4-keep-asking-how-does-this-help-me-achieve-my-goal">4. Keep asking, “how does this help me achieve my goal?”</h3>
<p>As opportunities come and go, a clear set of goals can help to determine which opportunities are important and which are not.</p>
<p>Take Facebook, for instance. You could ask, “how does <a target="_blank" href="http://www.theverge.com/a/mark-zuckerberg-future-of-facebook/aquila-drone-internet">creating flying internet drones</a> help them to connect people?”. One possible answer is that internet drones could help get more people connected to the internet, meaning that more people can access Facebook to connect with others.</p>
<p>While answering this question can be quite objective, it at least helps to start the conversation. It can also help prevent you from getting caught up in all the small, menial tasks. If you can’t come up with a reasonable answer as to why that small task helps you achieve your goals, move on to the next task.</p>
<p>Given that it’s just me starting my business, my time is quite limited. So, creating a set of goals for myself and the business will help me to decide which opportunities to take and which to pass on.</p>
<h3 id="heading-5-you-are-not-your-company-and-your-company-is-not-you">5. You are not your company and your company is not you</h3>
<p>If you’re contracting or doing freelance work, your company’s main product right now is you. But if your plan is for that to not always be the case, it’s important to treat your company as a separate entity.</p>
<p>Defining Technology will post about articles from news outlets or pieces I’ve written on various social media channels. It has a personality and a set of interests, making it easier for people to connect and engage with it.</p>
<p>While this is still in early stages, I will be continuing to grow Defining Technology’s social media presence going forward.</p>
<h3 id="heading-6-when-it-comes-to-time-choose-quality-over-quantity">6. When it comes to time, choose quality over quantity</h3>
<p>Working 80 hours a week does not mean necessarily that you will get twice the work done compared with if you’d worked 40 hours. It’s important to prioritise tasks, instead of trying to create more time to complete them.</p>
<p>It’s easy to devote all your time to working, but often this just leads to burnout. The hard part is being able to switch off and spend time with friends and family. Go see a movie, or go to your favourite restaurant. This is something I’ve paid particular attention to and will be keeping in mind in future months.</p>
<h3 id="heading-7-you-wont-get-everything-right">7. You won’t get everything right</h3>
<p>When starting a business, you will have to make decisions with not very much information. If you later realise that it was the wrong decision, it can be demoralising.</p>
<p>I’ve certainly made mistakes and will definitely make more going forward. But, I make a conscious effort to understand why it was the wrong decision and learn from it, so that I don’t make the same mistake in the future.</p>
<p>If you’re just starting a business, I hope that these points are of some use. I’d love to hear any comments or suggestions on starting a new business, so feel free to add a comment, or <a target="_blank" href="http://www.defining.tech/about/">get in touch</a>.</p>
]]></content:encoded></item><item><title><![CDATA[LawHack: Improving Access to Justice, Using Technology]]></title><description><![CDATA[Waiting for the hackathon to begin
A couple of weeks ago I took part in the Tech4Justice Hackathon, organised by the Law Society of Scotland. The aim of the weekend was to bring together people from different backgrounds to think about ways in which ...]]></description><link>https://blog.kbremner.com/lawhack-improving-access-to-justice-using-technology</link><guid isPermaLink="true">https://blog.kbremner.com/lawhack-improving-access-to-justice-using-technology</guid><category><![CDATA[community]]></category><category><![CDATA[startup]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Mon, 04 Jul 2016 05:24:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816416917/501567b5-e1b3-45ab-b150-74b466b2cdef.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Waiting for the hackathon to begin</p>
<p>A couple of weeks ago I took part in the <a target="_blank" href="http://www.eventbrite.co.uk/e/tech4justice-hackathon-registration-25904674579">Tech4Justice Hackathon</a>, organised by the <a target="_blank" href="http://www.lawscot.org.uk/">Law Society of Scotland</a>. The aim of the weekend was to bring together people from different backgrounds to think about ways in which technology could be used to improve access to justice.</p>
<h3 id="heading-the-problem">The Problem</h3>
<p>Many people still access legal advice in much the same way as they would have 20 years ago. The only thing that’s changed is the use of Google and email instead of the Yellow Pages and a telephone.</p>
<p>The process involves frantic searching to try and contact as many firms as possible that might be able to help with your particular issue and wait for some of them to reply. Then, you have to hope that you can afford their hourly rate (usually charged in 6 minute intervals). All this before anyone has given you any advice.</p>
<p>The reality is that consumers generally only think about access to legal services when some life event occurs. In these situations, they normally have little knowledge of the situation they are in and need advice. But when they try to get advice, they discover that they have to break out the wallet first.</p>
<p>What we’re left with is a situation where the customer is left holding all of the uncertainty and risk. Very cushy for the law firms, not so cushy for the consumer. This in-balance in any market drives a want for innovation from one side, but a fear of it from the other.</p>
<h3 id="heading-the-solution">The Solution</h3>
<p>There are already a number of startups attempting to address this in-balance. One example is <a target="_blank" href="https://www.quicklegal.com/">QuickLegal</a>, a startup that lets people have a video chat with a legal expert. The first 15 minutes is free and customers can pay for more advice after that. You can even get fixed price quotes for certain legal services.</p>
<p>This commoditisation of legal services has many law firms worried, and indeed this happens in any industry when innovation challenges the status-quo (just look at the effect Tesla is having in the automative industry).</p>
<p>When charging based on an hourly/daily rate, if a law firm can provide a service quicker, it makes less on providing that service. However, the commoditisation of legal services encourages a move towards value-based pricing — the price of providing a service is based on the perceived benefit by the consumer, rather than the actual cost of providing the service.</p>
<p>This change in pricing model encourages law firms to be more efficient. If they can provide a service quicker, they make more on providing that service.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816418699/025c6b8e-0ebe-44f7-837a-40af151c6879.png" alt /></p>
<h3 id="heading-lawchat">LawChat</h3>
<p>Over the course of the weekend, the team I was in built <a target="_blank" href="http://law-chat.xyz">LawChat</a>, a bot built on top of Facebook Messenger for accessing legal advice.</p>
<p>We wanted to think about how we could reduce the friction involved in accessing legal advice. The idea behind LawChat is that it could provide basic advice at any time of day or night, even helping users to complete basic tasks like filling out forms. In more complex cases, LawChat could also help hand users over to reputable law firms who could provide further advice and assistance. LawChat in turn would make a commission from law firms for referring that user to them. The user would provide a review for the law firm after receiving advice, which would be used to rank the results shown to other users.</p>
<p>By building LawChat on top of Facebook’s Messenger platform, it meant that we could bypass having to build and host a user interface for LawChat. This has the obvious saving in terms of time we didn’t have to spend building it, but it also meant that users don’t have to learn another user interface. They just interact with LawChat in the same way they interact with many of their friends — by chatting to it through Facebook Messenger.</p>
<p>To start talking to LawChat, a user just has to find the LawChat page on Facebook and click “message” — they don’t even have to like the page. No sign up or app install is required and the user can get basic advice in seconds. This ability to provide value quickly means that users are much more likely to engage with the service.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816420435/116cd4d4-1a73-477a-99c1-714eaa767c61.jpeg" alt /></p>
<p>Teams working hard on their concepts</p>
<h3 id="heading-future">Future</h3>
<p>Overall the weekend was a great success, with LawChat picking up the award for most disruptive idea. While we have no immediate plans to pursue our idea further, the law industry is at a point where there are many opportunities to innovate and disrupt and I would definitely take part in any similar events in the future.</p>
<p>I also will be thinking more about the part bots can play in any of the concepts I am currently working on. The ability to engage users quickly on a platform they already know how to use, while removing the need to build a user interface is very appealing and I’ll be watching the bot space very closely from now on.</p>
]]></content:encoded></item><item><title><![CDATA[Smart cards, HiFi’s and Oil & Gas… Why I Started my Own Business]]></title><description><![CDATA[I have not failed. I’ve just found 10,000 ways that won’t work.

Thomas Edison


My career up until now has been, let’s say, different from most.
Since graduating from the University of Glasgow in 2013 with an MEng degree in Electronics & Software En...]]></description><link>https://blog.kbremner.com/smart-cards-hifis-and-oil-gas-why-i-started-my-own-business</link><guid isPermaLink="true">https://blog.kbremner.com/smart-cards-hifis-and-oil-gas-why-i-started-my-own-business</guid><category><![CDATA[startup]]></category><category><![CDATA[Entrepreneurship]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Wed, 01 Jun 2016 15:16:12 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>I have not failed. I’ve just found 10,000 ways that won’t work.</p>
<ul>
<li>Thomas Edison</li>
</ul>
</blockquote>
<p>My career up until now has been, let’s say, different from most.</p>
<p>Since graduating from the University of Glasgow in 2013 with an MEng degree in Electronics &amp; Software Engineering, I’ve worked in a few different industries.</p>
<p>I started off working for <a target="_blank" href="http://www.ecebs.com/">Ecebs</a>, a company who’s technology is behind some of the biggest smart ticketing schemes in the UK and further afield. I developed Android apps that could read (and update) smart cards that conformed to the ITSO (<a target="_blank" href="https://www.itso.org.uk/">Interoperable Transport Standards Organisation</a>) specification (that’s every transport smart card in the UK). I also created <a target="_blank" href="https://play.google.com/store/apps/details?id=com.ecebs.cardviewer&amp;hl=en">Smart Ticket Checker</a>, a free Android app that can read any ITSO-compatible smart card and display the information in a user-friendly way.</p>
<p>From there, I moved to <a target="_blank" href="http://www.linn.co.uk/">Linn</a>. They use cutting edge technology to produce the highest quality audio from their systems. I worked on <a target="_blank" href="http://www.linn.co.uk/software#kazoo">Kazoo</a>, a cross-platform app for controlling your Linn HiFi system that works on OSX, Windows, Linux, iOS and Android. The app is used by owners of Linn equipment all around the world.</p>
<p>I then moved in to Oil &amp; Gas by joining <a target="_blank" href="http://www.streamba.net/">Streamba</a>, a company attempting to radically change the industry by building the “Google of the energy supply chain”. They leverage big data to identify where their user’s stuff is in the supply chain and how it got there.</p>
<p>And on Wednesday 24th May 2016, I started <a target="_blank" href="http://defining.tech">Defining Technology Ltd</a>.</p>
<blockquote>
<p>A pessimist sees the difficulty in every opportunity; an optimist sees the opportunity in every difficulty.</p>
<ul>
<li>Winston Churchill</li>
</ul>
</blockquote>
<p>My decision to walk away from each of those opportunities wasn’t driven by the companies themselves. I got to work with some very talented people on some interesting projects. But in every job, I always felt that something was missing.</p>
<p>When I’m waiting to pay at the supermarket, I don’t just see the queue. I see the potential for there to be no queue. What if there was a card-only checkout? What if trolleys were designed to encourage people to pack as they go? What if the trolley knew what was in it?</p>
<p>I’m constantly thinking of possible solutions to problems I face everyday and I’ve carried this way of thinking in to every job I’ve had. From stacking shelves at Tesco to optimising the energy supply chain in the North Sea, how can we do this better?</p>
<p>There’s a term used for organisations that look at themselves in this kind of way. They are known as “<a target="_blank" href="http://infed.org/mobi/peter-senge-and-the-learning-organization/#_The_learning_organization">learning organisations</a>”:</p>
<blockquote>
<p>[learning organizations are] organizations where people continually expand their capacity to create the results they truly desire, where new and expansive patterns of thinking are nurtured, where collective aspiration is set free, and where people are continually learning to see the whole together</p>
<ul>
<li>Peter Senge</li>
</ul>
</blockquote>
<p>It’s not that traditional organisation don’t go through change, it’s just done in a much more big-bang kind of way. The cost of change is high, putting more pressure on the need for the change to be successful, thus increasing the time taken to implement the change to try and make sure that it works. When change can be applied and tested in small chunks, the cost is reduced and then so too is the pressure for it to be successful. If it doesn’t work, revert and try a different approach.</p>
<p>The problem when you think like a learning organisation when working for an organisation that doesn’t is that it leads to frustration. Frustration at a lack of control. Frustration at the inability to test new approaches to try and improve the way the organisation works. You know things could be better, but have no power to influence change.</p>
<p>I soon realised that indeed what I was looking for was to be part of an organisation that identifies and approaches problems in the same way I do and is willing to give people, like me, the power to try new approaches to solving those problems.</p>
<blockquote>
<p>The way to get started is to quit talking and begin doing.</p>
<ul>
<li>Walt Disney</li>
</ul>
</blockquote>
<p>I knew that the sure fire way to become part of a learning organisation was to start my own. Run my own business. Make mistakes and learn from them. I could take my passion for identifying solutions to everyday problems and try to monetise those solutions.</p>
<p>But I had a bad case of imposter syndrome. I moved from job to job thinking I was looking for the last piece of the puzzle. “I just need that bit of experience, then I’ll be ready”. The day where I had all the experience and knowledge I felt I needed never came.</p>
<p>My drive to identify solutions to problems has led me to attend meetups in Glasgow and Edinburgh, like <a target="_blank" href="http://www.codecraftuk.org/">CodeCraft</a> and <a target="_blank" href="https://rookieoven.com/meetup/">RookieOven</a>. I even started <a target="_blank" href="http://buildbetter.tech">BuildBetter</a>, a series of all day events aimed at bringing people together to practice building better software.</p>
<p>Through attending these events, I’ve met people with all kinds of backgrounds and experiences. People I could discuss my frustrations with and hear about what they have gone through in starting their own businesses.</p>
<p>Eventually I realised that the only person stopping me from starting a business was my self. If I could believe in the skills I have and my ability to adapt and learn, then I could overcome the feeling of needing to know everything.</p>
<blockquote>
<p>Once we accept our limits, we go beyond them.</p>
<ul>
<li>Albert Einstein</li>
</ul>
</blockquote>
<p>2016 has been a year of doing for me. I proposed to my now-fiancée, I travelled to Vietnam to see a completely different culture and now I’ve started a business.</p>
<p>And it all came down to a simple decision. A decision to stop focussing on the reasons for why not to do something and instead focus on the positives for doing it.</p>
<p>It will be an interesting few months, but I’m definitely looking forward to the challenge.</p>
<p>Want to find out how Defining Technology can help you? <a target="_blank" href="mailto:kyle@defining.tech">Lets grab a coffee</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Adding a Back Button for React Native WebView]]></title><description><![CDATA[Sometimes when writing an app, we need to show some web page to the user. We may want them to be able to interact with the page, but also return to the page we originally showed to them. In this case, you may find yourself adding some kind of back bu...]]></description><link>https://blog.kbremner.com/adding-a-back-button-for-react-native-webview</link><guid isPermaLink="true">https://blog.kbremner.com/adding-a-back-button-for-react-native-webview</guid><category><![CDATA[React Native]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Wed, 18 May 2016 13:24:33 GMT</pubDate><content:encoded><![CDATA[<p>Sometimes when writing an app, we need to show some web page to the user. We may want them to be able to interact with the page, but also return to the page we originally showed to them. In this case, you may find yourself adding some kind of back button to your app.</p>
<p>The React Native <a target="_blank" href="https://facebook.github.io/react-native/docs/webview.html">WebView</a> gives us all the information we need to be able to link a back button to it, although it may not be immediately obvious from the docs how to do this. This guide will run through an example of how to do this.</p>
<p>A working example can be found on React Native Playground <a target="_blank" href="https://rnplay.org/apps/9qShmg">here</a> and a repository with a working example project can be found <a target="_blank" href="https://github.com/defining-technology/react-native-backbutton">here</a>.</p>
<p>A stylesheet containing the styles referenced in the code snippets below can be found in the React Native Playground example mentioned above.</p>
<h3 id="heading-setting-up-the-webview">Setting up the WebView</h3>
<p>Before we can add the button, we need to have a WebView that shows the content in the first place. So, presuming that you’re starting with a new project, lets replace the example components in the render method of index.android.js (and/or index.ios.js) with the following:</p>
<pre><code>&lt;View style={styles.container}&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">WebView</span>
    <span class="hljs-attr">ref</span>=<span class="hljs-string">{WEBVIEW_REF}</span>
    <span class="hljs-attr">style</span>=<span class="hljs-string">{{flex:</span> <span class="hljs-attr">1</span>}}
    <span class="hljs-attr">onNavigationStateChange</span>=
      <span class="hljs-string">{this.onNavigationStateChange.bind(this)}</span>
    <span class="hljs-attr">source</span>=<span class="hljs-string">{{uri:</span> ‘<span class="hljs-attr">https:</span>//<span class="hljs-attr">google.com</span>’}}
    /&gt;</span></span> 
 &lt;/View&gt;
</code></pre><p>We’ve done a few things here:</p>
<ul>
<li>Set the source property so that the WebView will load <a target="_blank" href="http://google.com,">http://google.com</a></li>
<li>Added a ref property so we can easily get a hold of the WebView later on</li>
<li>Added a callback for onNavigationStateChanged</li>
</ul>
<p>Right now, lets focus on the onNavigationStateChanged callback. The WebView will call this callback every time the WebView’s state changes. At any one time, the WebView can either be idle, loading or in an error state (for instance, if the request timed out).</p>
<p>When this callback is called, it is passed on object containing information about the page that the user is currently looking at. So lets implement the callback to get ahold of this information:</p>
<pre><code>onNavigationStateChange(navState) {
  <span class="hljs-built_in">this</span>.setState({
    <span class="hljs-attr">canGoBack</span>: navState.canGoBack
  });
}
</code></pre><p>Here, we’ve pulled out the canGoBack property and set it in our component’s state. This property tells us if the user has navigated past the original page we loaded and so can go back towards the original page.</p>
<p>There are other properties that you can pull out, such as whether the page is still loading or not and the URL of the current page. You could log navState to console to take a closer look if you’re interested in what other information it contains that you could make use of. For now, we have everything we need to add the back button.</p>
<h3 id="heading-adding-the-back-button">Adding the Back Button</h3>
<p>Thanks to our callback, we now have a value in our component’s state that tells us whether we should have an enabled back button. We still need to actually add a button that the user can press to go back. So, we’ll add the following above the WebView component in the root View:</p>
<pre><code>&lt;View style={styles.topbar}&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">TouchableOpacity</span>
    <span class="hljs-attr">disabled</span>=<span class="hljs-string">{!this.state.canGoBack}</span>
    <span class="hljs-attr">onPress</span>=<span class="hljs-string">{this.onBack.bind(this)}</span>
    &gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Text</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{styles.topbarText}</span>&gt;</span>Go Back<span class="hljs-tag">&lt;/<span class="hljs-name">Text</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">TouchableOpacity</span>&gt;</span></span>
&lt;/View&gt;
</code></pre><p>We’ve added a wrapper View so that the button is shown in a top bar above the WebView. Then, we’ve wrapped the Text component in a TouchableOpacity component and added a callback so that we can handle when the user presses it. Finally, we’ve linked up the disabled property of the TouchableOpacity component to our canGoBack state property so that the user can’t press the button if they are on the original page.</p>
<p>The final step is to link the WebView and the back button by implementing the onBack method referenced above:</p>
<pre><code>onBack() {
  <span class="hljs-built_in">this</span>.refs[WEBVIEW_REF].goBack();
}
</code></pre><p>Simple, eh? We’ve used the reference we added to the WebView at the start to get a hold of it and then simply called it’s goBack method to navigate back.</p>
<p>That’s it! You’ve now got a back button for your WebView. The full example can be found on React Native Playground <a target="_blank" href="https://rnplay.org/apps/9qShmg">here</a> or in a repository <a target="_blank" href="https://github.com/defining-technology/react-native-backbutton">here</a>.</p>
<p>If you have any questions, feel free to post below!</p>
]]></content:encoded></item><item><title><![CDATA[Automating Android Unit Tests with Xamarin]]></title><description><![CDATA[Recently, I’ve been evaluating Xamarin’s native bindings. As part of this, I started looking at how to run unit tests on real devices automatically as part of continuous integration (CI) builds. This is fairly well documented for iOS (see here). Howe...]]></description><link>https://blog.kbremner.com/automating-android-unit-tests-with-xamarin</link><guid isPermaLink="true">https://blog.kbremner.com/automating-android-unit-tests-with-xamarin</guid><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Tue, 11 Aug 2015 20:28:27 GMT</pubDate><content:encoded><![CDATA[<p>Recently, I’ve been evaluating <a target="_blank" href="http://kbremner.github.io/2015/03/14/xamarin.com">Xamarin’s</a> native bindings. As part of this, I started looking at how to run unit tests on real devices automatically as part of continuous integration (CI) builds. This is fairly well documented for iOS (see <a target="_blank" href="http://developer.xamarin.com/guides/ios/deployment,_testing,_and_metrics/touch.unit/">here</a>). However, there is no similar documentation for Android. This post is an attempt to fill that gap. I’ll be using Xamarin Studio on Mac, but the steps should be similar if you’re using Visual Studio.</p>
<blockquote>
<p>This post is about automated unit testing, as opposed to integration/acceptance testing. For details of running UI tests as part of an acceptance testing strategy, see <a target="_blank" href="http://developer.xamarin.com/guides/testcloud/uitest/">Xamarin.UITest</a>.</p>
</blockquote>
<h4 id="heading-creating-the-project">Creating the Project</h4>
<p>With Xamarin Studio open, click <em>File &gt; New &gt; Solution…</em> and select <strong>Android Unit Test Project</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816408953/6d8a138f-0ccb-4daa-b965-86e4183a7a01.png" alt /></p>
<p>This will generate two source files, MainActivity.cs and TestsSample.cs. TestsSample.cs will look familiar if you’ve written NUnit tests before — it uses annotations to mark methods as tests and includes a number of examples. Under the covers Xamarin use NUnitLite for Android and iOS unit testing, which exposes the same annotations as NUnit and the same basic assertions. More information about the differences between NUnit and NUnitLite can be found <a target="_blank" href="https://github.com/nunit/dev/wiki/NUnitLite-Unification">here</a>.</p>
<p>With MainActivity.cs, there are two interesting points. Firstly, it extends TestSuiteActivity.cs, which handles the execution of the tests. Secondly, the AddTest method is used to add tests to be executed from an assembly. The example passes in the currently executing assembly, but the tests could come from another referenced assembly.</p>
<p>Running this sample will show an activity with buttons for running the tests and changing some options. After running the tests, the results will be shown on screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816410866/53597c5b-6baf-4b71-a907-5f2c73456c2f.png" alt /></p>
<h4 id="heading-automating-the-tests">Automating the Tests</h4>
<p>We know how to run the tests manually, but how can we automate this? The documentation for unit testing on iOS points to the <a target="_blank" href="http://iosapi.xamarin.com/?link=T%3aNUnit.UI.TouchRunner">TouchRunner class</a>. The documentation for this class shows that it’s quite simple to setup the iOS unit tests to be automated:</p>
<pre><code class="lang-csharp">runner.Writer = <span class="hljs-keyword">new</span> TcpTextWriter (<span class="hljs-string">"10.0.1.2"</span>, <span class="hljs-number">16384</span>);
<span class="hljs-comment">// start running the test suites as soon as the application is loaded</span>
runner.AutoStart = <span class="hljs-literal">true</span>;
<span class="hljs-comment">// crash the application (to ensure it's ended) and return to springboard</span>
runner.TerminateAfterExecution = <span class="hljs-literal">true</span>;
</code></pre>
<p>The AutoStart property of the runner is set to true to tell it to start running the tests once the configuration is complete. Setting the TerminateAfterExecution property to true ensures that the application does not stay running after the tests have been completed.</p>
<p>The final step is to actually get the results off of the device, which is where the Writer property comes in. The runner will write basic information about the device that it is running the tests on, as well the results of the tests, using the value of the Writer property. The default Writer writes the results to the console, but a TcpTextWriter is available that can send the results to a server.</p>
<blockquote>
<p>Collecting the results and dealing with them is beyond the scope of this post, but a simple TCP server can be written in a few lines using Python that would be suitable for the job. A CI build just needs to start the server before executing the tests, ensuring that the unit test application knows the IP address and port for the TCP server.</p>
</blockquote>
<p>The approach is not quite so straight forward on Android (the lack of documentation doesn’t help the matter). The first issue is that on Android there are no similar properties. The TestSuiteActivity only exposes a method for adding tests. When the application is running, we can see that there is an <em>Options</em> button which, when pressed, shows an activity that allows for setting the details of a remote server.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1774816412742/b301bc76-cd8f-484c-a3d4-0df5e74a9bd3.png" alt /></p>
<p>Setting these values to point to a TCP server and running the tests shows that the device details and test results are sent to the server. The code that is executing the tests has to be getting the host name and port somehow, so it’s time to start digging.</p>
<p>Right clicking TestSuiteActivity and clicking <em>Go To Declaration</em> results in the class being shown in the assembly browser. Setting language to C# in the top right-hand corner of the window shows a fairly readable version of the implementation. From the generated MainActiviy class, we can see that the work to start the execution of the tests must be happening in the OnCreate base method, so that seems like a good place to start:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnCreate</span>(<span class="hljs-params">Bundle bundle</span>)</span>
{
    <span class="hljs-keyword">base</span>.OnCreate (bundle);
    ...
    <span class="hljs-keyword">bool</span> booleanExtra = <span class="hljs-keyword">this</span>.Intent.GetBooleanExtra (<span class="hljs-string">"automated"</span>, <span class="hljs-literal">false</span>);
    <span class="hljs-keyword">if</span> (booleanExtra) {
        AndroidRunner.Runner.Options.LoadFromBundle (<span class="hljs-keyword">this</span>.Intent.Extras);
    }
    ...
    <span class="hljs-keyword">if</span> (booleanExtra) {
        ThreadPool.QueueUserWorkItem (<span class="hljs-keyword">delegate</span> {
            Log.Info (<span class="hljs-string">"NUnitLite"</span>, <span class="hljs-string">"NUnit automated tests started"</span>);
            AndroidRunner.Runner.Run (<span class="hljs-keyword">this</span>.current_test, <span class="hljs-keyword">this</span>);
            Log.Info (<span class="hljs-string">"NUnitLite"</span>, <span class="hljs-string">"NUnit automated tests completed"</span>);
            <span class="hljs-keyword">this</span>.Finish ();
        });
    }
    Log.Info (<span class="hljs-string">"NUnitLite"</span>, <span class="hljs-string">"NUnit automated tests loaded."</span>);
}
</code></pre>
<p>Here we start to see some interesting stuff. The method gets a boolean extra from the intent called “automated”. This is then used to decide whether to load additional options from the extras. The value is used again later on to determine whether to automatically run the tests and finish the activity when the tests have completed, or wait for the user to manually run the tests. So, we know that adding a boolean called “automated” to the intent’s extras that is set to true will cause the tests to be automatically run. But how do we get the results to be sent to a waiting server? The key is in the AndroidRunner.Runner.Options.LoadFromBundle method:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">LoadFromBundle</span>(<span class="hljs-params">Bundle bundle</span>)</span>
{
    <span class="hljs-keyword">this</span>.EnableNetwork = bundle.GetBoolean (<span class="hljs-string">"remote"</span>, <span class="hljs-literal">false</span>);
    <span class="hljs-keyword">this</span>.HostName = (bundle.GetString (<span class="hljs-string">"hostName"</span>) ?? <span class="hljs-string">"0.0.0.0"</span>);
    <span class="hljs-keyword">this</span>.HostPort = bundle.GetInt (<span class="hljs-string">"hostPort"</span>, <span class="hljs-number">-1</span>);
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> ShowUseNetworkLogger
{
    <span class="hljs-keyword">get</span>
    {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.EnableNetwork &amp;&amp; !<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace (<span class="hljs-keyword">this</span>.HostName) &amp;&amp; <span class="hljs-keyword">this</span>.HostPort &gt; <span class="hljs-number">0</span>;
    }
}
</code></pre>
<p>This provides the last piece of the puzzle. The host name and port are also pulled from the intent’s extras. The property ShowUseNetworkLogger, which is based on the “remote” extra, is used in AndroidRunner to determine whether to create a TcpTextWriter for writing the results, or just write to the console.</p>
<p>We can bring all this knowledge together in to a new class:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AutomatedTestSuiteActivity</span> : <span class="hljs-title">TestSuiteActivity</span>
{
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnCreate</span>(<span class="hljs-params">Bundle bundle</span>)</span>
    {
        Intent.PutExtra (<span class="hljs-string">"automated"</span>, <span class="hljs-literal">true</span>);
        Intent.PutExtra (<span class="hljs-string">"remote"</span>, <span class="hljs-literal">true</span>);
        Intent.PutExtra (<span class="hljs-string">"hostName"</span>, HostName);
        Intent.PutExtra (<span class="hljs-string">"hostPort"</span>, HostPort);
        <span class="hljs-keyword">base</span>.OnCreate (bundle);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Finish</span> (<span class="hljs-params"></span>)</span>
    {
        System.Environment.Exit (<span class="hljs-number">0</span>);
    }

    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">string</span> HostName { <span class="hljs-keyword">get</span>; }
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">int</span> HostPort { <span class="hljs-keyword">get</span>; }
}
</code></pre>
<p>This class now handles setting all the appropriate extras, all the extending class has to do is implement properties for the host name and port. Additionally, the Finish method has been overridden so that it forces the process to exit, ensuring that the application does not hang around after the tests have finished.</p>
<p>I hope that this post is useful to anyone else that is investigating automated Android unit tests with Xamarin and feel free to use the AutomatedTestSuiteActivity.</p>
<h4 id="heading-references">References</h4>
<p><a target="_blank" href="http://kbremner.github.io/2015/03/14/xamarin.com">Xamarin</a></p>
<p><a target="_blank" href="http://developer.xamarin.com/guides/ios/deployment,_testing,_and_metrics/touch.unit/">iOS Unit Testing — Xamarin</a></p>
<p><a target="_blank" href="https://github.com/nunit/dev/wiki/NUnitLite-Unification">NUnitLite Unification — nunit/dev wiki</a></p>
<p><a target="_blank" href="http://iosapi.xamarin.com/?link=T%3aNUnit.UI.TouchRunner">NUnit.UI.TouchRunner</a></p>
]]></content:encoded></item><item><title><![CDATA[Android and Client Authentication]]></title><description><![CDATA[Recently I have been developing an application that had to support client authentication using certificates. The process wasn’t quite as well documented as I would have expected, so this post aims to help others that need to support client authentica...]]></description><link>https://blog.kbremner.com/android-and-client-authentication</link><guid isPermaLink="true">https://blog.kbremner.com/android-and-client-authentication</guid><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Tue, 11 Aug 2015 20:15:22 GMT</pubDate><content:encoded><![CDATA[<p>Recently I have been developing an application that had to support client authentication using certificates. The process wasn’t quite as well documented as I would have expected, so this post aims to help others that need to support client authentication in their application (aimed at Ice Cream Sandwich (ICS) and above).</p>
<h4 id="heading-system-keystore">System Keystore</h4>
<p>Traditionally, Android applications created their own keystores for storing sensitive credentials. ICS, however, brought in the ability for applications to access credentials stored in a system keystore when authorised by a user. This not only simplifies the process, but with some devices supporting hardware-backed keystores, it can also be more secure than an application keystore stored in the file system.</p>
<blockquote>
<p>If credentials using a particular algorithm are stored using a hardware feature, such as a secure element or Trusted Execution Environment (TEE), they are effectively “bound” to that particular device once installed and so protected against extraction. To determine if an algorithm is hardware-backed, the method <a target="_blank" href="https://developer.android.com/reference/android/security/KeyChain.html#isBoundKeyAlgorithm%28java.lang.String%29">KeyChain.isBoundKeyAlgorithm(String)</a> can be used.</p>
</blockquote>
<h4 id="heading-getting-an-alias">Getting an Alias</h4>
<p>Before an application can use credentials stored in the system keystore, the user needs to give the application access to them. The KeyChain API provides a simple means of doing this:</p>
<pre><code class="lang-java">KeyChain.choosePrivateKeyAlias (activity, <span class="hljs-keyword">new</span> KeyChainAliasCallback(){
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">alias</span><span class="hljs-params">(String alias)</span> </span>{
    <span class="hljs-keyword">if</span>(alias != <span class="hljs-keyword">null</span>) {
      <span class="hljs-comment">// Do something with the selected alias</span>
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">// User didn't select an alias, do something</span>
    }
  }
}, <span class="hljs-keyword">null</span>, <span class="hljs-keyword">null</span>, <span class="hljs-keyword">null</span>, -<span class="hljs-number">1</span>, <span class="hljs-keyword">null</span>);
</code></pre>
<p>The user will be shown a dialog where they can select a certificate currently stored in the system keystore, or install a new certificate. When the user selects a certificate and closes the dialog, the callback will be given an alias for the selected certificate that it can use to access it. If the user cancels the dialog, the callback will be given a null alias.</p>
<blockquote>
<p>It is important to note that although the KeyChain API allows an application to provide hints as to which certificate the user should choose, it cannot force the user to select a particular certificate.</p>
</blockquote>
<p>Once an application has an alias for a certificate, it can be used to obtain information associated with it. Below is an example implementation of an X509KeyManager that uses the alias for a certificate to get it’s certificate chain and private key. This can then be used to initialise an SSL context, before setting a URL connection to use the SSL context to allow it to support client authentication, as shown below. Note that the X509Impl instance should be cached instead of being created for each connection, as getting the certificate chain and private key can be slow.</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.deftech.spotify;

<span class="hljs-keyword">import</span> java.net.Socket;
<span class="hljs-keyword">import</span> java.security.KeyManagementException;
<span class="hljs-keyword">import</span> java.security.NoSuchAlgorithmException;
<span class="hljs-keyword">import</span> java.security.Principal;
<span class="hljs-keyword">import</span> java.security.PrivateKey;
<span class="hljs-keyword">import</span> java.security.cert.CertificateException;
<span class="hljs-keyword">import</span> java.security.cert.X509Certificate;

<span class="hljs-keyword">import</span> javax.net.ssl.HttpsURLConnection;
<span class="hljs-keyword">import</span> javax.net.ssl.KeyManager;
<span class="hljs-keyword">import</span> javax.net.ssl.SSLContext;
<span class="hljs-keyword">import</span> javax.net.ssl.X509KeyManager;

<span class="hljs-keyword">import</span> android.content.Context;
<span class="hljs-keyword">import</span> android.security.KeyChain;
<span class="hljs-keyword">import</span> android.security.KeyChainException;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">X509Impl</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">X509KeyManager</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String alias;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> X509Certificate[] certChain;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> PrivateKey privateKey;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> SSLContext <span class="hljs-title">setForConnection</span><span class="hljs-params">(HttpsURLConnection con, Context context, String alias)</span> <span class="hljs-keyword">throws</span> CertificateException, KeyManagementException </span>{
        SSLContext sslContext = <span class="hljs-keyword">null</span>;
        <span class="hljs-keyword">try</span> {
            sslContext = SSLContext.getInstance(<span class="hljs-string">"TLS"</span>);
        } <span class="hljs-keyword">catch</span>(NoSuchAlgorithmException e){
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Should not happen..."</span>, e);
        }
        sslContext.init(<span class="hljs-keyword">new</span> KeyManager[] { fromAlias(context, alias)}, <span class="hljs-keyword">null</span>, <span class="hljs-keyword">null</span>);
        con.setSSLSocketFactory(sslContext.getSocketFactory());
        <span class="hljs-keyword">return</span> sslContext;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> X509Impl <span class="hljs-title">fromAlias</span><span class="hljs-params">(Context context, String alias)</span> <span class="hljs-keyword">throws</span> CertificateException </span>{
        X509Certificate[] certChain;
        PrivateKey privateKey;
        <span class="hljs-keyword">try</span> {
            certChain = KeyChain.getCertificateChain(context, alias);
            privateKey = KeyChain.getPrivateKey(context, alias);
        } <span class="hljs-keyword">catch</span> (KeyChainException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> CertificateException(e);
        } <span class="hljs-keyword">catch</span> (InterruptedException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> CertificateException(e);
        }
        <span class="hljs-keyword">if</span>(certChain == <span class="hljs-keyword">null</span> || privateKey == <span class="hljs-keyword">null</span>){
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> CertificateException(<span class="hljs-string">"Can't access certificate from keystore"</span>);
        }
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> X509Impl(alias, certChain, privateKey);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">X509Impl</span><span class="hljs-params">(String alias, X509Certificate[] certChain, PrivateKey privateKey)</span> <span class="hljs-keyword">throws</span> CertificateException </span>{
        <span class="hljs-keyword">this</span>.alias = alias;
        <span class="hljs-keyword">this</span>.certChain = certChain;
        <span class="hljs-keyword">this</span>.privateKey = privateKey;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">chooseClientAlias</span><span class="hljs-params">(String[] arg0, Principal[] arg1, Socket arg2)</span> </span>{
        <span class="hljs-keyword">return</span> alias;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> X509Certificate[] getCertificateChain(String alias) {
        <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>.alias.equals(alias)) <span class="hljs-keyword">return</span> certChain;
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> PrivateKey <span class="hljs-title">getPrivateKey</span><span class="hljs-params">(String alias)</span> </span>{
        <span class="hljs-keyword">if</span>(<span class="hljs-keyword">this</span>.alias.equals(alias)) <span class="hljs-keyword">return</span> privateKey;
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }

    <span class="hljs-meta">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> String <span class="hljs-title">chooseServerAlias</span><span class="hljs-params">(String keyType, Principal[] issuers, Socket socket)</span> </span>{
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnsupportedOperationException();
    }

    <span class="hljs-meta">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> String[] getClientAliases(String keyType, Principal[] issuers) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnsupportedOperationException();
    }

    <span class="hljs-meta">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> String[] getServerAliases(String keyType, Principal[] issuers) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnsupportedOperationException();
    }
}
</code></pre>
<h4 id="heading-references">References</h4>
<p><a target="_blank" href="https://developer.android.com/reference/android/security/KeyChain.html">android.security.KeyChain</a></p>
<p><a target="_blank" href="http://android-developers.blogspot.co.uk/2012/03/unifying-key-store-access-in-ics.html">Unifying Key Store Access in ICS</a></p>
<p><a target="_blank" href="https://github.com/android/platform_packages_apps_email/blob/master/emailcommon/src/com/android/emailcommon/utility/SSLUtils.java">Android Email Client — SSLUtils.java</a></p>
]]></content:encoded></item><item><title><![CDATA[Hello Spotify]]></title><description><![CDATA[Recently I got round to having a look at Spotify’s developer library, libspotify, to try and figure out how to use it within Android apps. As part of this, I decided to create a “Hello Spotify” example project that might be useful to others looking t...]]></description><link>https://blog.kbremner.com/hello-spotify</link><guid isPermaLink="true">https://blog.kbremner.com/hello-spotify</guid><dc:creator><![CDATA[Kyle Bremner]]></dc:creator><pubDate>Tue, 11 Aug 2015 20:07:27 GMT</pubDate><content:encoded><![CDATA[<p>Recently I got round to having a look at Spotify’s developer library, <a target="_blank" href="https://developer.spotify.com/technologies/libspotify/">libspotify</a>, to try and figure out how to use it within Android apps. As part of this, I decided to create a “Hello Spotify” example project that might be useful to others looking to use the library.</p>
<p>I have created a simple example library project that is available on GitHub <a target="_blank" href="https://github.com/kbremner/Hello-Spotify">here</a>. It only provides a means of logging in, but shows how to use libspotify on Android. The pre-requisites for using it are:</p>
<ul>
<li>Android NDK installed (see <a target="_blank" href="http://developer.android.com/tools/sdk/ndk/index.html">here</a>)</li>
<li>Spotify premium account</li>
<li>API key (see <a target="_blank" href="https://developer.spotify.com/technologies/libspotify/keys/">here</a>)</li>
</ul>
<p>Below is a description of the different components of the project and the steps I followed to make it.</p>
<h4 id="heading-get-libspotify">Get libspotify</h4>
<p>The first task is to download the Android version of libspotify from the libspotify homepage. The required file for compiling is in the lib directory of the download. Take the largest file and remove the numbers after .so (this is version information that isn’t supported by Android). Also in the download is the header file that will be needed later, under include/libspotify/api.h. Within the Hello Spotify example project, the .so file can be found under jni/libspotify, and the header under jni/headers.</p>
<h4 id="heading-get-api-key">Get API key</h4>
<p>So, now we have the library. Before we can do anything with it, we need the API key, so the C code version should be downloaded and placed in the jni folder as <em>appkey.c</em>.</p>
<h4 id="heading-write-the-makefile-androidmk">Write the makefile (Android.mk)</h4>
<p>Now we have almost everything we need to start working with libspotify. To be able to make the code, we need a build script, Android.mk:</p>
<pre><code class="lang-makefile">LOCAL_PATH := <span class="hljs-variable">$(<span class="hljs-built_in">call</span> my-<span class="hljs-built_in">dir</span>)</span>

<span class="hljs-keyword">include</span> <span class="hljs-variable">$(CLEAR_VARS)</span>
LOCAL_MODULE    := libspotify\spotify-prebuilt
LOCAL_SRC_FILES := libspotify\libspotify.so
<span class="hljs-keyword">include</span> <span class="hljs-variable">$(PREBUILT_SHARED_LIBRARY)</span>

<span class="hljs-keyword">include</span> <span class="hljs-variable">$(CLEAR_VARS)</span>
LOCAL_LDLIBS    := -llog
LOCAL_SHARED_LIBRARIES := libspotify\spotify-prebuilt
LOCAL_MODULE    := hello_spotify
LOCAL_SRC_FILES := appkey.c hello_spotify.c callbacks.c
<span class="hljs-keyword">include</span> <span class="hljs-variable">$(BUILD_SHARED_LIBRARY)</span>
</code></pre>
<p>The Android build tool uses this makefile to compile the modules before putting them in the libs directory to be used by the project. There are a couple of interesting things in the Android.mk file within the project:</p>
<ul>
<li><strong>PREBUILT_SHARED_LIBRARY</strong> — Android needs you to tell it what shared libraries you have, giving them a module name which can be referenced later in the makefile</li>
<li><strong>BUILD_SHARED_LIBRARY</strong> — Here, the shared library previously named spotify-prebuilt is referenced so that Android knows it is a build dependency. We also give the new module the name hello_spotify, which we will come across later</li>
<li><strong>LOCAL_LDLIBS</strong> — Android also needs you to tell it which additional libraries you are using. The project uses Android’s logging helper, so we have to include the library.</li>
<li><strong>LOCAL_MODULE</strong> — the LOCAL_MODULE defines the name of the module. In the PREBUILT section, this is the name to be used to refer to the prebuilt libraries. In the BUILD section, it defines the name for the new library that is to be built.</li>
</ul>
<p>Now we have everything we need to start writing the actual code!</p>
<p>In a project like this where we are creating a bridge between C code and Java code, there needs to be some “glue”. This glue is written as C methods that are then referenced in the Java code. For the Java compiler to find the native methods, the C method names have to comply to a strict naming convention. This is best explained by example.</p>
<p>Say we have a class, JNIExample, that wants to make a reference to a native method, getString(), that simply returns a string. To do this, JNIExample would look like this:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.a.b;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JNIExample</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">native</span> String <span class="hljs-title">getString</span><span class="hljs-params">()</span></span>;    
}
</code></pre>
<p>Here, we see that the native keyword has been used to tell the compiler that the method is linked to a C method. But from here, the compiler has to make the link between the C method and the Java method. To do this, the method name has to follow the convention <em>Java</em>_<em></em>_<em></em>_<em></em>, as seen below:</p>
<pre><code class="lang-c"><span class="hljs-function">JNIEXPORT jstring JNICALL <span class="hljs-title">Java_com_a_b_JNIExample_getString</span><span class="hljs-params">(JNIEnv *, jobject)</span></span>;
</code></pre>
<p>The JNIEXPORT and JNICALL allow the method to be called from a Java class. The method naming convention allows the compiler to link the native method to the method in the class that it relates to. The JNIEnv and jobject parameters can be used by the method to more easily interact with the Java code. Finally, as the method returns a String, the return type is a jstring (the JNIEnv provides methods for converting between char*’s and jstrings).</p>
<p>It is best not to try and write the C header yourself. The Java tool javah can create the appropriate C header file by pointing it to a compiled Java class that makes references to native methods. It will take care of all the parameter and return type conversion, as well as the naming convention.</p>
<p>There is an alternative, which is to use JNI_OnLoad to register the native methods, but that’s beyond the scope of this article. For more details about this approach, see <a target="_blank" href="http://sbcgamesdev.blogspot.co.uk/2012/12/using-jnionload-in-adroid-ndk.html">here</a>.</p>
<h4 id="heading-writing-it">Writing it</h4>
<p>The Hello Spotify project just has 3 C files:</p>
<ul>
<li><strong>appkey.c</strong> — your app key obtained from Spotify’s website</li>
<li><strong>callbacks.c</strong> — the callback methods used by libspotify to notify us of events</li>
<li><strong>hello_spotify.c</strong> — holds our JNI glue</li>
</ul>
<p>As for headers, we have:</p>
<ul>
<li><strong>callbacks.h</strong> — defines what callbacks we have implemented</li>
<li><strong>com_deftech_spotify_HelloSpotify.h</strong> — the javah-generated header file</li>
</ul>
<p>The callback methods mainly just log using the Android logging methods. The only one that is a bit different is the <strong>logged_in</strong> callback, that takes some of the logic from one of the libspotify examples to log some details about the logged in user.</p>
<p>The interesting stuff here is in hello_spotify.c. First, lets look at the method for getting the build ID:</p>
<pre><code class="lang-c"><span class="hljs-function">JNIEXPORT jstring JNICALL <span class="hljs-title">Java_com_deftech_spotify_HelloSpotify_getBuildID</span><span class="hljs-params">(JNIEnv *env, jobject jObject)</span> </span>{
    <span class="hljs-keyword">return</span> (*env)-&gt;NewStringUTF(env, sp_build_id());
}
</code></pre>
<p>Here, we see the use of the JNIEnv to convert a char* returned by the libspotify method sp_build_id() in to a jstring that can be returned by the method in the Java class.</p>
<p>Now we’ve seen a simple method, we can move on to the more complicated log in method. To log in, we first need to create a session. To create a session, we need to provide a sp_session_config struct filled with config values. These include the location to be used as cache, the app key, the callbacks, etc. Once that has been filled, the session can be created by calling sp_session_create, passing it the config settings and a double pointer that it can set to point to an allocated session.</p>
<p>Once the session is created, we can login with the provided username and password. If this was successful, we can then create a background thread for calling sp_session_process_events.</p>
<p>This background thread is important, as libspotify has many internal threads that are synchronized by pushing tasks that are executed when the sp_session_process_events method is called by an application thread. It relies on this thread listening for calls to the notify_main_thread callback, as that is libspotify saying that there are events to be processed. Additionally, the application needs to make sure that no other libspotify methods are called while sp_session_process_events is executing. As this process can get difficult to manage, I thought it was best to leave it out of this simple example. The psyonspotify example in the references shows one of it doing it.</p>
<p>Note that the approach for the login method is naive in a number of ways:</p>
<ul>
<li>from the libspotify documentation here, it states that it is not supported to have multiple active sessions, and it’s recommended to only call this [sp_create_session] once per process. It would be better to have an init method that creates a session that can be used later for operations like logging in a user. This is why if you attempted to call login a few times your app will eventually crash.</li>
<li>The main_loop thread doesn’t respect the timeout or the notify_main_thread callback.</li>
<li>The “blob” that can be used for remembering users isn’t handled.</li>
</ul>
<h4 id="heading-building-it">Building it</h4>
<p>The NDK code has to be compiled from the command line. Navigate to the root of the project and simply run ndk-build. This will compile the code, outputting any build errors. I found it was best to clean the project in eclipse first, run ndk-build, then refresh the eclipse project.</p>
<p>Now that we’ve written and compiled our C code, the next step is the Java class to wrap the native code, HelloSpotify:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.deftech.spotify;

<span class="hljs-keyword">import</span> android.content.Context;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloSpotify</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String cacheDir;
    <span class="hljs-keyword">static</span> {
        System.loadLibrary(<span class="hljs-string">"hello_spotify"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">HelloSpotify</span><span class="hljs-params">(Context context)</span></span>{
        cacheDir = context.getCacheDir().getAbsolutePath();
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">login</span><span class="hljs-params">(String username, String password)</span></span>{
        <span class="hljs-keyword">return</span> login(username, password, cacheDir);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">native</span> String <span class="hljs-title">getBuildID</span><span class="hljs-params">()</span></span>;
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">native</span> <span class="hljs-keyword">int</span> <span class="hljs-title">login</span><span class="hljs-params">(String username, String password, String cacheDir)</span></span>;
}
</code></pre>
<p>This class is quite simple, as most of the logic of mapping the C methods to the Java methods has already been done. The important bit is the static call to load the library name specified in Android.mk. But that’s it for the Java code!</p>
<p>This example project can be cloned from GitHub and (after putting in your appkey.c) used as a library project by other Android projects. It provides a simple look at how to use a native library by writing the JNI glue to call it from a Java class.</p>
<p>Any comments/queries, just drop a comment below!</p>
<ul>
<li><a target="_blank" href="https://developer.spotify.com/docs/libspotify/12.1.45/examples.html">libspotify examples</a></li>
<li><a target="_blank" href="https://github.com/spotify/psyonspotify">GitHub: spotify/psyonspotify</a></li>
</ul>
]]></content:encoded></item></channel></rss>