<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/android/</id>
  <link href="https://www.endpointdev.com/blog/tags/android/"/>
  <link href="https://www.endpointdev.com/blog/tags/android/" rel="self"/>
  <updated>2021-06-30T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Appium: Automated Mobile Testing</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2021/06/appium-automated-mobile-testing/"/>
      <id>https://www.endpointdev.com/blog/2021/06/appium-automated-mobile-testing/</id>
      <published>2021-06-30T00:00:00+00:00</published>
      <author>
        <name>Couragyn Chretien</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2021/06/appium-automated-mobile-testing/clouds.jpg&#34; alt=&#34;Clouds and a river&#34;&gt;&lt;/p&gt;
&lt;!-- Image by Seth Jensen --&gt;
&lt;p&gt;First impressions are everything. You can have the best, most robust application in the world, but if it looks like it’s from 2004 most users won’t give it a second look. Automated testing can help ensure that the app the user sees is consistent and fully functional no matter the iteration.&lt;/p&gt;
&lt;p&gt;Selenium, Cypress, and other automated testing suites have become more and more popular for webapps. This trend has not carried over to mobile native app testing. This may be a bit surprising, as a fully functional frontend can be the difference between a professional-feeling app and a hacky one.&lt;/p&gt;
&lt;p&gt;There are many frameworks that can be used to test mobile applications (Appium, UI Automator, Robotium, XCUITest, SeeTest, and TestComplete to name a few), but today we’ll be focusing on &lt;a href=&#34;https://appium.io/&#34;&gt;Appium&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Appium is an open source framework that’s easy to use out of the box. It can be used to test many versions of many different mobile OSes. It’s a one-stop shop to ensure your users are on the same page, no matter the platform.&lt;/p&gt;
&lt;h3 id=&#34;installation-and-setup-for-linux&#34;&gt;Installation and setup for Linux&lt;/h3&gt;
&lt;h4 id=&#34;general&#34;&gt;General&lt;/h4&gt;
&lt;p&gt;We will be testing the To-Do List app from the Google Play Store. What we’re testing and for which platform isn’t important, since we’re here to learn about the Appium methodology that can be applied to any mobile app.&lt;/p&gt;
&lt;h4 id=&#34;avd-android-emulator&#34;&gt;AVD Android emulator&lt;/h4&gt;
&lt;p&gt;We will be using an AVD (Android Virtual Device) to test our app. There are many out there but I recommend &lt;a href=&#34;https://developer.android.com/studio&#34;&gt;Android Studio’s&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once it’s running, just click &lt;code&gt;Configure &amp;gt; AVD Manager&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/appium-automated-mobile-testing/avd-manager.jpg&#34; alt=&#34;AVD Manager&#34;&gt;&lt;/p&gt;
&lt;p&gt;Either create a new AVD or use the default one and start it up.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/appium-automated-mobile-testing/avd-view.jpg&#34; alt=&#34;AVD View&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;appium&#34;&gt;Appium&lt;/h4&gt;
&lt;p&gt;Download Appium from &lt;a href=&#34;https://github.com/appium/appium-desktop/releases/tag/v1.21.0&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Appium can be run through the command line but we’re going to use the desktop app here. This gives you the ability to record a session and capture elements for automated tests.&lt;/p&gt;
&lt;p&gt;Once it’s running, all we have to do is click &lt;code&gt;Start Server&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/appium-automated-mobile-testing/start-appium.png&#34; alt=&#34;Start Appium&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;ruby-and-rspec&#34;&gt;Ruby and RSpec&lt;/h4&gt;
&lt;p&gt;We will be using Ruby and &lt;a href=&#34;https://rspec.info/&#34;&gt;RSpec&lt;/a&gt; to run our tests. At the top level of our project we need to add these lines to our Gemfile:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/Gemfile&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;source &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;https://www.rubygems.org&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gem &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;appium_lib&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;~&amp;gt; 11.2&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gem &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;appium_lib_core&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;~&amp;gt; 4.2&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Make sure Ruby is installed, then run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gem install rspec
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ bundle install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We also need to configure RSpec to find the correct AVD and APK to test.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/spec/spec_settings.rb&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;ANDROID_PACKAGE&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;io.appium.android.apis&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;android_caps&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;caps&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# These settings match the settings for your AVD. They can be found in the AVD Manager.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;platformName&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Android&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;platformVersion&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;11&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;deviceName&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Pixel_3a_API_30_x86&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;#  This points to the apk you are testing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;app&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;./To-do_list.apk&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;automationName&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;UIAutomator2&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;appium_lib&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;wait&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally we need to create our test. Since we don’t know the elements yet we can start with a blank template.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/spec/TaskCRUD&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;require&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;spec_settings&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;require&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;appium_lib&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;describe &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Tests basic CRUD functions of Task application&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  before(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:all&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt; = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Appium&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Driver&lt;/span&gt;.new(android_caps, &lt;span style=&#34;color:#080&#34;&gt;true&lt;/span&gt;).start_driver
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  after(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:all&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt;&amp;amp;.quit
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  it &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;...&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;putting-it-all-together&#34;&gt;Putting it all together&lt;/h4&gt;
&lt;p&gt;Let’s boot it up and make sure everything is running as expected.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run an AVD from Android Studio&lt;/li&gt;
&lt;li&gt;Start the Appium Server&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;rspec&lt;/code&gt; in the project root&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Even though there is no test written it should still run and pass the template test. Running it the first time will save the APK into the AVD virtual memory. We can now access the app on the AVD emulator and record tests.&lt;/p&gt;
&lt;h3 id=&#34;recording-a-test-with-inspector&#34;&gt;Recording a test with Inspector&lt;/h3&gt;
&lt;p&gt;Open the Appium window that has the server running and click Start Inspector Session:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/appium-automated-mobile-testing/start-inspector.png&#34; alt=&#34;Start Inspector&#34;&gt;&lt;/p&gt;
&lt;p&gt;Under &lt;code&gt;Attach to Session...&lt;/code&gt; you will see a dropdown containing the RSpec test we just tried to run. If it’s not here try running &lt;code&gt;rspec&lt;/code&gt; again. Select this and click the &lt;code&gt;Attach to Session&lt;/code&gt; button.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/appium-automated-mobile-testing/attach-to-session.png&#34; alt=&#34;Attach to Session&#34;&gt;&lt;/p&gt;
&lt;p&gt;We are now connected to the Android Emulator via the Inspector. We can click on elements to see their properties and interact with them.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/appium-automated-mobile-testing/view-element.jpg&#34; alt=&#34;View Element&#34;&gt;&lt;/p&gt;
&lt;p&gt;If we click the top record button it will save our actions and allow us to import them into a test.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2021/06/appium-automated-mobile-testing/session-recording.jpg&#34; alt=&#34;Session Recording&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;running-a-test&#34;&gt;Running a test&lt;/h3&gt;
&lt;p&gt;Let’s take what we got from the Inspector and populate our template test.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/spec/TaskCRUD&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;require&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;spec_settings&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;require&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;appium_lib&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;describe &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Tests basic CRUD functions of Task application&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  before(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:all&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt; = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Appium&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Driver&lt;/span&gt;.new(android_caps, &lt;span style=&#34;color:#080&#34;&gt;true&lt;/span&gt;).start_driver
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  after(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:all&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt;&amp;amp;.quit
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  it &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;creates a task and verifies its name&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   el1 = &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt;.find_elements(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:id&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;todolist.scheduleplanner.dailyplanner.todo.reminders:id/zk&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el1.click
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el2 = &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt;.find_elements(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:id&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;todolist.scheduleplanner.dailyplanner.todo.reminders:id/xd&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el2.click
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el3 = &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt;.find_elements(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:id&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;todolist.scheduleplanner.dailyplanner.todo.reminders:id/wt&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el3.send_keys &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;task_1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el4 = &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt;.find_elements(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:id&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;todolist.scheduleplanner.dailyplanner.todo.reminders:id/wr&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el4.click
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el5 = &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt;.find_elements(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:xpath&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/androidx.recyclerview.widget.RecyclerView/android.widget.LinearLayout[3]/android.widget.TextView&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el5.click
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el6 = &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt;.find_elements(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:id&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;todolist.scheduleplanner.dailyplanner.todo.reminders:id/wo&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el6.click
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el7 = &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt;.find_elements(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:xpath&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/androidx.recyclerview.widget.RecyclerView&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el7.click
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    el8 = &lt;span style=&#34;color:#33b&#34;&gt;@driver&lt;/span&gt;.find_elements(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:id&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;todolist.scheduleplanner.dailyplanner.todo.reminders:id/xl&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expect(el8.text).to eql &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Test-text-1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If we run it with the &lt;code&gt;rspec&lt;/code&gt; command we can watch the test in progress on the AVD and then we receive a success message!&lt;/p&gt;
&lt;h3 id=&#34;whats-next&#34;&gt;What’s next?&lt;/h3&gt;
&lt;h4 id=&#34;additional-tests&#34;&gt;Additional tests&lt;/h4&gt;
&lt;p&gt;Tests should be written to cover as many aspects of the app as possible. The main focus should be on parts of the app that don’t change very often. Tests written for something in progress or that is due for a facelift will need to be rewritten when the time comes.&lt;/p&gt;
&lt;h4 id=&#34;test-with-other-devices&#34;&gt;Test with other devices&lt;/h4&gt;
&lt;p&gt;An app can look perfect on a Galaxy S20 but look awful on a Pixel 5. That’s why it’s important to try many different AVD versions for your app. Android allows you to create many phone types that run many different firmware versions.&lt;/p&gt;
&lt;h4 id=&#34;cicd-integration&#34;&gt;CI/​CD integration&lt;/h4&gt;
&lt;p&gt;Manual testing is great but nothing beats automation. These tests should ideally be integrated into a continuous integration and continuous delivery (CI/​CD) platform. This way each build will be automatically vetted for bugs.&lt;/p&gt;
&lt;p&gt;For more info, check out this &lt;a href=&#34;https://circleci.com/blog/ci-for-mobile-app-development/&#34;&gt;guide for integrations with CircleCI&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Mobile Device and Application Management (MAM vs. MDM)</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2017/12/mam-vs-mdm/"/>
      <id>https://www.endpointdev.com/blog/2017/12/mam-vs-mdm/</id>
      <published>2017-12-20T00:00:00+00:00</published>
      <author>
        <name>Joe Marrero</name>
      </author>
      <content type="html">
        &lt;p&gt;Businesses of all sizes have been increasingly using mobile devices for all kinds of activities. Some of these activities are pretty common, like being able to read and respond to work email and manage work calendars. On the other hand, some companies are using mobile devices for more specific niche activities like checking in customers, using device cameras to read barcodes and UPC stickers, or even temporarily storing sensitive business related data.&lt;/p&gt;
&lt;p&gt;As a result of the proliferation of mobile devices for work use, corporations and smaller businesses are finding the need the exhibit finer control over how their employees and customers use their devices. This is where mobile device management (MDM) and mobile application management (MAM) really shine and help solve many of these types of problems.&lt;/p&gt;
&lt;h3 id=&#34;what-is-mdm&#34;&gt;What is MDM?&lt;/h3&gt;
&lt;p&gt;Mobile device management (MDM) is a software solution that allows organizations to manage the maintenance, deployment, and configuration of mobile devices that are issued to members of the organization. All of this magic tends to be done via a &lt;em&gt;store&lt;/em&gt; or &lt;em&gt;portal&lt;/em&gt; application that users download onto their devices.&lt;/p&gt;
&lt;p&gt;The app begins an enrollment process when the user enters their credentials that the organization previously issued to the user (usually the user’s email and password). After the device is enrolled, the user gets prompted to accept if the device should be managed as a &lt;em&gt;work&lt;/em&gt; device. After the enrollment completes, the store app can push configuration, some device level policies for enforcement, and any required applications.&lt;/p&gt;
&lt;p&gt;Some of the useful things you can do with MDM:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Install apps autonomously:&lt;/strong&gt; Some corporations have their own suite of mobile applications that they require all employees to use. MDM allows organizations to push down required apps to a device. When a newer app is available, the new app gets pushed down to devices automatically. This makes it easier to deploy apps to all employees and ensure users have the latest apps on their devices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration:&lt;/strong&gt; Push down sensitive or complex network configuration. Organizations can push down WiFi and VPN configurations. This is especially useful for allowing users to connect to work networks that have hidden SSIDs. You can also enforce use of PIN, and other security features.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Policy Enforcement:&lt;/strong&gt; Set global policies that can affect all or a portion of devices; Prevent third party apps from being installed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditing:&lt;/strong&gt; See what devices have done, what versions of apps are deployed, and how a device has used your network.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scale up:&lt;/strong&gt; MDM scales up nicely for thousands of devices. If you find yourself updating devices one at time then you definitely need an MDM solution.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;what-is-mam&#34;&gt;What is MAM?&lt;/h3&gt;
&lt;p&gt;Mobile application management (MAM) is a technology that primarily focuses on enforcing policies on mobile applications. To be able to have this finer level control over applications, an organization needs to &lt;em&gt;wrap&lt;/em&gt; the application.&lt;/p&gt;
&lt;p&gt;Wrapping is the process of injecting code into an application despite not even having access to the application’s source code. The injected code can then listen for events that are related to certain policies. For example, if an application is not allowed to open the camera application, then the injected code will listen for the corresponding events that will bring up the camera.&lt;/p&gt;
&lt;p&gt;How this is done is different on Android and iOS because the platforms handle these kinds things quite differently.&lt;/p&gt;
&lt;p&gt;Some of the useful things you can do with MDM:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Block certain applications from being able to handle certain types of data.&lt;/li&gt;
&lt;li&gt;Enforce filesystem level encryption even if the application was never designed to use encryption.&lt;/li&gt;
&lt;li&gt;Detect if an application is running on a rooted device and kill the app if desired.&lt;/li&gt;
&lt;li&gt;Force an application to use a VPN all the time or when outside of the allowed networks.&lt;/li&gt;
&lt;li&gt;Block devices from accessing the Internet or the local loop-back addresses (i.e. 127.0.0.1 or ::1).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;what-are-some-mam-and-mdm-vendors&#34;&gt;What are some MAM and MDM vendors?&lt;/h3&gt;
&lt;p&gt;Some of the major vendors of MAM and MDM solutions are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.citrix.com/products/xenmobile/&#34;&gt;XenMobile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.air-watch.com&#34;&gt;AirWatch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.mobileiron.com&#34;&gt;MobileIron&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;XenMobile and AirWatch are robust and mature MAM and MDM solutions. With respect to features, both offer single-sign on (SSO), a suite of productivity apps (email, browser, etc.), and mobile content management.&lt;/p&gt;
&lt;p&gt;XenMobile’s email client is similar in capabilities to AirWatch’s email client. However, AirWatch also allows management of the native email clients on Android and iOS which tends to be buggier. Furthermore, AirWatch’s productivity apps may require additional VMware product licenses to unlock some of these capabilities.&lt;/p&gt;
&lt;p&gt;In terms of the administration web app, XenMobile is a bit more complicated than AirWatch and may require a few tech support calls to get configured just right. Both products require mobile apps to be &lt;em&gt;wrapped&lt;/em&gt; with a &lt;em&gt;wrapping utility&lt;/em&gt; before they can be uploaded to the management console. And both offer SDKs that allow organizations to build apps that have MAM capabilities without needing to be wrapped.&lt;/p&gt;
&lt;p&gt;Also, both products have great VPN capabilities. If your organization is already using Netscaler than it might make more sense to use XenMobile. Netscaler appears to be more feature-rich compared to VMware Tunnel, but Netscaler is also very hard to configure. With respect to price, AirWatch tends to be cheaper for smaller organizations than XenMobile because XenMobile offers fixed price tiers where as AirWatch has a per-device pricing model.&lt;/p&gt;
&lt;p&gt;MobileIron is a bit less developed than XenMobile and AirWatch. However, this also makes it a bit more simpler and easier to administrate. In addition, MobileIron lacks a managed email application compared to XenMobile and AirWatch.&lt;/p&gt;
&lt;h3 id=&#34;need-help&#34;&gt;Need Help?&lt;/h3&gt;
&lt;p&gt;End Point has tons of experience with MAM and MDM. If your organization needs expert-level consulting then we are available to help you!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Reconciling Android source code</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2017/12/android-reconciling-source/"/>
      <id>https://www.endpointdev.com/blog/2017/12/android-reconciling-source/</id>
      <published>2017-12-19T00:00:00+00:00</published>
      <author>
        <name>Zed Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;Recently, a client came to us with an interesting problem. They needed some changes made to an internal-use Android app that had been created for them by another company, but they didn’t have up-to-date source code for the app, and they had no way of getting it. They gave us an old archive of source code, a more recent working build of the app, and asked us to figure it out.&lt;/p&gt;
&lt;p&gt;The working version of the app they sent us was built in early 2016, and the app was compiled for Android SDK version 22, which is 5.1 Lollipop. It came out in March 2015, so it was a year old even when the app was created, and the code was using lots of features which have since been deprecated.&lt;/p&gt;
&lt;p&gt;After putting this source code in a Git repository, I started by building the app from the source they’d sent and comparing it to the working version. It looked mostly the same, but a couple of features were broken. At the suggestion of a coworker, I used &lt;a href=&#34;https://github.com/Konloch/bytecode-viewer/releases&#34;&gt;BytecodeViewer&lt;/a&gt; to decompile the APK and took a look around.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2017/12/android-reconciling-source/bytecode-viewer.jpg&#34; alt=&#34;BytecodeViewer screenshot&#34;&gt;&lt;/p&gt;
&lt;p&gt;It was a little while before I was able to find any differences between the source we had and this decompiled code, but I did find a few logic differences. The source was also using a few old APIs that needed to be updated. With the needed changes made to the source code, I soon had a working build of the app that matched the functionality of the built APK we had.&lt;/p&gt;
&lt;p&gt;This wasn’t the only thing wrong with the app, however. Another hurdle in picking up this project from its previous developers showed up when I had to fix a few odd problems reported by the users. One problem was on an information screen listing attributes of items. An example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2017/12/android-reconciling-source/details.jpg&#34; alt=&#34;Details screen&#34;&gt;&lt;/p&gt;
&lt;p&gt;The “Quantity” field worked just fine for any non-zero value, but if it was 0, the app was displaying 16 instead. This turned out to be very simple to fix. Below is the layout code involved:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;TextView&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;android:layout_width=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;wrap_content&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;android:layout_height=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;wrap_content&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;android:textAppearance=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;?android:attr/textAppearanceMedium&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;android:text=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;16&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;android:textColor=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;@color/background_black&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;android:id=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;@+id/txt_info_quantity&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The default text of that field was, likely as a placeholder, set to 16, and it wasn’t being overwritten when the given value was 0. So, all I had to do was replace it with 0, since it would be overwritten anytime there was a nonzero value.&lt;/p&gt;
&lt;p&gt;This was a good reminder to somehow keep track of placeholders like this, for example, putting a todo comment or something similar, so bugs like this can be caught in code reviews.&lt;/p&gt;
&lt;p&gt;Figuring out how to reconcile the source and binary of this app seemed like a daunting task, but it turned out to be very doable, and was a very interesting project.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Simplifying mobile development with Ionic Framework</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/11/simplifying-mobile-development-with/"/>
      <id>https://www.endpointdev.com/blog/2014/11/simplifying-mobile-development-with/</id>
      <published>2014-11-06T00:00:00+00:00</published>
      <author>
        <name>Kamil Ciemniewski</name>
      </author>
      <content type="html">
        &lt;p&gt;My high school math teacher used to say that mathematicians are the laziest people on Earth. Why? Because they always look for clever ways to simplify their work.&lt;/p&gt;
&lt;p&gt;If you stop and think about it, all that technology is, is just simplification. It’s taking the infinitely complex world and turning it into something sterile and simple. It’s all about producing simple models with a limited number of elements and processes.&lt;/p&gt;
&lt;p&gt;Today I’d like to walk you through creation of a mobile app that could be used on iOS, Android or Windows Phone. We’ll use a very cool set of technologies that allow us to switch from using multiple languages and frameworks (Objective-C for iOS, Java for Android and C# for Windows Phone) to just using HTML, CSS and JavaScript.&lt;/p&gt;
&lt;p&gt;Let’s start turning complex into simple!&lt;/p&gt;
&lt;h3 id=&#34;phonegap-and-ionic-framework&#34;&gt;PhoneGap and Ionic Framework&lt;/h3&gt;
&lt;h4 id=&#34;creating-the-project&#34;&gt;Creating the project&lt;/h4&gt;
&lt;p&gt;In order to be able to start playing along, you need to get yourself a set of toys. Assuming that you’ve got NodeJS and Npm installed already, all you have to do is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npm install -g cordova ionic&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now you should be able to create the project’s scaffold. We’ll be creating a simple app that will list all the latest cartoons from &lt;a href=&#34;https://xkcd.com&#34;&gt;the xkcd blog&lt;/a&gt;. Let’s call it Cartoonic.&lt;/p&gt;
&lt;p&gt;Ionic comes with a handy tool called ‘ionic’. It allows you to create a new project as well as perform some automated project-structure-management tasks. The project creation task accepts a ‘skeleton’ name that drives an initial layout of the app. Possible options are: ‘blank’, ‘tabs’ and ‘sidemenu’.&lt;/p&gt;
&lt;p&gt;We’ll be creating an app from scratch so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ionic start Cartoonic blank&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The framework gives you an option of whether you want to use Sass or just plain old Css. To turn Sass on for the project run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#038&#34;&gt;cd&lt;/span&gt; Cartoonic &amp;amp;&amp;amp; ionic setup sass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All went well, but now let’s see if it &lt;em&gt;works&lt;/em&gt; well. For this, Ionic gives you an ability to test your app in the browser, as if it were a screen of your mobile device. To run the app in the browser now:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ionic serve&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/11/simplifying-mobile-development-with/image-0.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34; target=&#34;_blank&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/11/simplifying-mobile-development-with/image-0.png&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h4 id=&#34;working-with-the-app-layout&#34;&gt;Working with the app layout&lt;/h4&gt;
&lt;p&gt;We need to let all users know what a cool name we’ve chosen for our app. The default one provided by the scaffold wouldn’t work well. Also we’d like the color of the header to be blue instead of the default white.&lt;/p&gt;
&lt;p&gt;In order to do so you can take a look at the CSS documentation for different aspects of the UI:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://ionicframework.com/docs/components/#header&#34;&gt;https://ionicframework.com/docs/components/#header&lt;/a&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- a/www/index.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/index.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;@@ -21,8 +21,8 @@
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#666&#34;&gt;&lt;/span&gt;     &amp;lt;ion-pane&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-      &amp;lt;ion-header-bar class=&amp;#34;bar-stable&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-        &amp;lt;h1 class=&amp;#34;title&amp;#34;&amp;gt;Ionic Blank Starter&amp;lt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      &amp;lt;ion-header-bar class=&amp;#34;bar-positive&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        &amp;lt;h1 class=&amp;#34;title&amp;#34;&amp;gt;Cartoonic&amp;lt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;       &amp;lt;/ion-header-bar&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &amp;lt;ion-content&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &amp;lt;/ion-content&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/11/simplifying-mobile-development-with/image-1.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34; target=&#34;_blank&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/11/simplifying-mobile-development-with/image-1.png&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;So far so good, now let’s play with the list of cartoons:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- a/scss/ionic.app.scss
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/scss/ionic.app.scss
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+.cartoon {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  text-align: center;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  width: 96%;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  margin-left: 2%;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  margin-top: 2%;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  margin-bottom: 2%;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  img {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    width: 90%;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- a/www/index.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/index.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        &amp;lt;ion-list&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          &amp;lt;ion-item class=&amp;#34;item-divider&amp;#34;&amp;gt;Where Do Birds Go&amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          &amp;lt;ion-item class=&amp;#34;cartoon&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+            &amp;lt;img src=&amp;#34;http://imgs.xkcd.com/comics/where_do_birds_go.png&amp;#34; alt=&amp;#34;&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          &amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          &amp;lt;ion-item class=&amp;#34;item-divider&amp;#34;&amp;gt;Lightsaber&amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          &amp;lt;ion-item class=&amp;#34;cartoon&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+            &amp;lt;img src=&amp;#34;http://imgs.xkcd.com/comics/lightsaber.png&amp;#34; alt=&amp;#34;&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          &amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        &amp;lt;/ion-list&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;       &amp;lt;/ion-content&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/11/simplifying-mobile-development-with/image-2.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34; target=&#34;_blank&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/11/simplifying-mobile-development-with/image-2.png&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Alright, we’ve got the list that’s looking quite nice. The data behind is static, but for now we just wanted to make sure the look&amp;amp;feel is good.&lt;/p&gt;
&lt;h4 id=&#34;using-angularjs-to-manage-the-app&#34;&gt;Using AngularJS to manage the app&lt;/h4&gt;
&lt;p&gt;Ionic is built around the fantastic AngularJS framework. That’s our means of developing the logic behind. We need to make the list of cartoons use real data from the Xckd blog RSS feed. We also need to enable tapping on images to see the picture in the browser (so it can be zoomed in).&lt;/p&gt;
&lt;p&gt;Let’s start with making the UI use dynamically bound data that we can operate on with JavaScript. In order to do so, we need to add a controller for our view. We also need to specify the data binding between the markup we’ve created previously and the variable in the controller that we intend to use as our data store.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- a/www/index.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/index.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     &amp;lt;script src=&amp;#34;js/app.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    &amp;lt;script src=&amp;#34;js/controllers.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;   &amp;lt;/head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  &amp;lt;body ng-app=&amp;#34;starter&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  &amp;lt;body ng-app=&amp;#34;starter&amp;#34; ng-controller=&amp;#34;CartoonsCtrl&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(...)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &amp;lt;ion-content&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &amp;lt;ion-list&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-          &amp;lt;ion-item class=&amp;#34;item-divider&amp;#34;&amp;gt;Where Do Birds Go&amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-          &amp;lt;ion-item class=&amp;#34;cartoon&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            &amp;lt;img src=&amp;#34;http://imgs.xkcd.com/comics/where_do_birds_go.png&amp;#34; alt=&amp;#34;&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-          &amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-          &amp;lt;ion-item class=&amp;#34;item-divider&amp;#34;&amp;gt;Lightsaber&amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-          &amp;lt;ion-item class=&amp;#34;cartoon&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-            &amp;lt;img src=&amp;#34;http://imgs.xkcd.com/comics/lightsaber.png&amp;#34; alt=&amp;#34;&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          &amp;lt;ion-item class=&amp;#34;item-divider&amp;#34; ng-repeat-start=&amp;#34;cartoon in cartoons&amp;#34;&amp;gt;{{ cartoon.title }}&amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          &amp;lt;ion-item class=&amp;#34;cartoon&amp;#34; ng-repeat-end&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+            &amp;lt;img ng-src=&amp;#34;{{ cartoon.href }}&amp;#34; alt=&amp;#34;&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;           &amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &amp;lt;/ion-list&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &amp;lt;/ion-content&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- a/www/js/app.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/js/app.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-angular.module(&amp;#39;starter&amp;#39;, [&amp;#39;ionic&amp;#39;])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+angular.module(&amp;#39;starter&amp;#39;, [&amp;#39;ionic&amp;#39;, &amp;#39;starter.controllers&amp;#39;])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/js/controllers.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+angular.module(&amp;#39;starter.controllers&amp;#39;, [])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+.controller(&amp;#39;CartoonsCtrl&amp;#39;, function($scope) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  $scope.cartoons = [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      href: &amp;#34;http://imgs.xkcd.com/comics/where_do_birds_go.png&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      id: 1434,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      title: &amp;#34;Where Do Birds Go&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      href: &amp;#34;http://imgs.xkcd.com/comics/lightsaber.png&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      id: 1433,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      title: &amp;#34;Lightsaber&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  ];
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+});
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can notice that “ng-controller” directive has been added to the body element. It points at the newly created controller, which we’re loading with a script tag and making available to the rest of the app by including its module (starter.controllers) in the ‘starter’ module’s dependencies list.&lt;/p&gt;
&lt;p&gt;Let’s implement opening the picture upon the tap:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- a/www/index.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/index.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;       &amp;lt;ion-content&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &amp;lt;ion-list&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           &amp;lt;ion-item class=&amp;#34;item-divider&amp;#34; ng-repeat-start=&amp;#34;cartoon in cartoons&amp;#34;&amp;gt;{{ cartoon.title }}&amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-          &amp;lt;ion-item class=&amp;#34;cartoon&amp;#34; ng-repeat-end&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          &amp;lt;ion-item class=&amp;#34;cartoon&amp;#34; ng-repeat-end ng-click=&amp;#34;openCartoon(cartoon)&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;             &amp;lt;img ng-src=&amp;#34;{{ cartoon.href }}&amp;#34; alt=&amp;#34;&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           &amp;lt;/ion-item&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         &amp;lt;/ion-list&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- a/www/js/controllers.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/js/controllers.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;@@ -13,4 +13,8 @@ angular.module(&amp;#39;starter.controllers&amp;#39;, [])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#666&#34;&gt;&lt;/span&gt;       title: &amp;#34;Lightsaber&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  $scope.openCartoon = function(cartoon) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    window.open(cartoon.href, &amp;#39;_blank&amp;#39;, &amp;#39;location=no&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  };
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That was simple wasn’t it? We’ve just added the ng-click directive making the click/tap event bound to the openCartoon function from the scope. This function in turn is using window.open passing ‘_blank’ as target. Et voilà!&lt;/p&gt;
&lt;p&gt;Now, let’s implement loading images from the real feed:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- a/www/index.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/index.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;     &amp;lt;script src=&amp;#34;cordova.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    &amp;lt;script type=&amp;#34;text/javascript&amp;#34; src=&amp;#34;https://www.google.com/jsapi&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    &amp;lt;script type=&amp;#34;text/javascript&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      google.load(&amp;#34;feeds&amp;#34;, &amp;#34;1&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    &amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &amp;lt;!-- your app&amp;#39;s js --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &amp;lt;script src=&amp;#34;js/app.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &amp;lt;script src=&amp;#34;js/controllers.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    &amp;lt;script src=&amp;#34;js/services.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;   &amp;lt;/head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;body ng-app=&amp;#34;starter&amp;#34; ng-controller=&amp;#34;CartoonsCtrl&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- a/www/js/app.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/js/app.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-angular.module(&amp;#39;starter&amp;#39;, [&amp;#39;ionic&amp;#39;, &amp;#39;starter.controllers&amp;#39;])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+angular.module(&amp;#39;starter&amp;#39;, [&amp;#39;ionic&amp;#39;, &amp;#39;starter.controllers&amp;#39;, &amp;#39;starter.services&amp;#39;])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- a/www/js/controllers.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/js/controllers.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-angular.module(&amp;#39;starter.controllers&amp;#39;, [])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+angular.module(&amp;#39;starter.controllers&amp;#39;, [&amp;#39;starter.services&amp;#39;])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-.controller(&amp;#39;CartoonsCtrl&amp;#39;, function($scope) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  $scope.cartoons = [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-      href: &amp;#34;http://imgs.xkcd.com/comics/where_do_birds_go.png&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-      id: 1434,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-      title: &amp;#34;Where Do Birds Go&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-      href: &amp;#34;http://imgs.xkcd.com/comics/lightsaber.png&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-      id: 1433,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-      title: &amp;#34;Lightsaber&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  ];
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+.controller(&amp;#39;CartoonsCtrl&amp;#39;, function($scope, cartoons) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  $scope.cartoons = [];
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   $scope.openCartoon = function(cartoon) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     window.open(cartoon.href, &amp;#39;_blank&amp;#39;, &amp;#39;location=no&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  $scope.$watch(function() {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    return cartoons.list;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  }, function(list) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    $scope.cartoons = list;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;--- /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+++ b/www/js/services.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#666&#34;&gt;@@ -0,0 +1,26 @@
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#666&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+angular.module(&amp;#39;starter.services&amp;#39;, [])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+.factory(&amp;#39;cartoons&amp;#39;, function($rootScope) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  var self = {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    list: [],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    url: &amp;#34;http://xkcd.com/rss.xml&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    fetch: function() {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      var feed = new google.feeds.Feed(self.url);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      feed.load(function(result) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        $rootScope.$apply(function() {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          if(result.status.code == 200) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+            self.list = result.feed.entries.map(function(entry) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+              return {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+                href: entry.content.match(/src=&amp;#34;[^&amp;#34;]*/g)[0].substring(5, 100),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+                title: entry.title
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+              }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+            });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+          }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+        });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  };
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  self.fetch();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  return self;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+});
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Okay, a couple of comments here. You may wonder why have we loaded Google APIs? That’s because if we were to try to load the xml that comes from the blog’s feed, we would inevitably fail because of the “&lt;a href=&#34;https://en.wikipedia.org/wiki/Same-origin_policy&#34;&gt;Same Origin Policy&lt;/a&gt;”. Basically, the Ajax request would not complete successfully and there’s nothing we can do locally about it.&lt;/p&gt;
&lt;p&gt;Luckily, Google has created a service we can use as a middleman between our in-browser JavaScript code and blog’s web server. Long story made short: when you load the feed with Google’s Feed API - the data’s there and it’s also already parsed.&lt;/p&gt;
&lt;p&gt;We’re also adding a custom service here. The service fetches the entries upon its initialization. And because the controller’s depending on this service - we’re guaranteed to get the data as soon as the controller is initialized. The controller is also using the $watch function to make sure it has the most recent copy of the entries list.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/11/simplifying-mobile-development-with/image-3.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34; target=&#34;_blank&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/11/simplifying-mobile-development-with/image-3.png&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ionicframework.com/docs/&#34;&gt;Ionic docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developers.google.com/feed/v1/&#34;&gt;Google Feed API docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Same-origin_policy&#34;&gt;Same Origin Policy on Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://xkcd.com&#34;&gt;The xkcd blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Android Developer Tools via Google Chrome</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/06/android-developer-tools-via-google/"/>
      <id>https://www.endpointdev.com/blog/2014/06/android-developer-tools-via-google/</id>
      <published>2014-06-11T00:00:00+00:00</published>
      <author>
        <name>Zed Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;Recently I was working on a website on my Android phone, and I found myself needing Chrome’s Developer Tools. However, Developer Tools are not included in the Android version of Chrome for many reasons, including lack of screen real estate.&lt;/p&gt;
&lt;p&gt;So, I looked around, and I found a solution: using a USB cable and ADB (&lt;a href=&#34;https://developer.android.com/tools/help/adb.html&#34;&gt;Android Debug Bridge&lt;/a&gt;), you can do debugging on an Android device with Chrome’s Developer Tools &lt;em&gt;from your desktop.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To show you exactly what I mean, here’s a short video demonstrating this:&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/ut7NWQZVXEk?rel=0&#34; frameborder=&#34;0&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;So, how does one work this magic? There are several ways, but I’ll talk about the one that I used. For this method, you need to have Google Chrome version 31 or higher installed on both your Android device and your development machine.&lt;/p&gt;
&lt;p&gt;First, you have to enable Android debugging on your device. From &lt;a href=&#34;https://developer.android.com/tools/device.html&#34;&gt;android.com&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;On most devices running Android 3.2 or older, you can find the option under &lt;strong&gt;Settings &amp;gt; Applications &amp;gt; Development&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On Android 4.0 and newer, it’s in &lt;strong&gt;Settings &amp;gt; Developer options&lt;/strong&gt;.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Note:&lt;/strong&gt; On Android 4.2 and newer, &lt;strong&gt;Developer options&lt;/strong&gt; is hidden by default. To make it available, go to &lt;strong&gt;Settings &amp;gt; About phone&lt;/strong&gt; and tap &lt;strong&gt;Build number&lt;/strong&gt; seven times. Return to the previous screen to find &lt;strong&gt;Developer options&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;Next, connect your device with a USB cable and, on your development machine, go to &lt;strong&gt;about:inspect&lt;/strong&gt; and check &lt;strong&gt;Discover USB Devices&lt;/strong&gt;. After a second or two, your device should show up like this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2014/06/android-developer-tools-via-google/image-0-big.jpeg&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/06/android-developer-tools-via-google/image-0.jpeg&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To open Dev Tools for a tab, just click “inspect” below it. The buttons next to “inspect” only appear for tabs open in Chrome, but you can open Developer Tools for any app that uses WebView, whether it’s in Chrome or not.&lt;/p&gt;
&lt;p&gt;And there you go! Fully featured Chrome Developer Tools for your Android device on your development machine. More information, including a way to do this with earlier versions of Chrome, can be found at the &lt;a href=&#34;https://developer.chrome.com/devtools/docs/remote-debugging&#34;&gt;Android Developer site&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>CSS Conf US 2014 — Part Two</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/06/css-conf-us-2014-part-two/"/>
      <id>https://www.endpointdev.com/blog/2014/06/css-conf-us-2014-part-two/</id>
      <published>2014-06-04T00:00:00+00:00</published>
      <author>
        <name>Greg Davidson</name>
      </author>
      <content type="html">
        &lt;h3 id=&#34;more-thoughts-on-getting-vertical-testing-and-icon-fonts&#34;&gt;More Thoughts on Getting Vertical, Testing and Icon Fonts&lt;/h3&gt;
&lt;p&gt;Without further ado I’ve written up another batch of my notes about three more great talks at CSS Conf US in Amelia Island, Florida last week.&lt;/p&gt;
&lt;h3 id=&#34;antoine-butler--embrace-the-vertical&#34;&gt;Antoine Butler — Embrace the Vertical&lt;/h3&gt;
&lt;p&gt;Antoine shared his observation that vertical media queries are available to CSS developers but not
often used. With the &lt;a href=&#34;https://opensignal.com/reports/fragmentation-2013/&#34;&gt;vast array&lt;/a&gt;
of devices accessing the web today vertical media queries can be a useful tool to adapt your content effectively. Antoine walked us through a couple examples of how he applied this technique in a couple of his projects. The first was a prototype of WikiPedia. While they have gone with a separated mobile site (e.g. en.m.wikipedia.org/), he started with the HTML from the desktop site and applied some vertical media queries to make the content much more digestible. Take a look at &lt;a href=&#34;https://codepen.io/aebsr/pen/BapraL&#34;&gt;his code&lt;/a&gt; to see how it works.&lt;/p&gt;
&lt;p&gt;The second example Antoine demonstrated was for the navigation at &lt;a href=&#34;http://www.vw.com/&#34;&gt;Volkswagen&lt;/a&gt;. The client wanted to display an unlimited number of items in the secondary navigation. Once again Antoine applied vertical media queries to handle the varying number of navigation elements based on the device height. Check out his &lt;a href=&#34;https://codepen.io/aebsr/pen/MWpjoM&#34;&gt;adaptive sticky vertical navigation code&lt;/a&gt; for a closer look.&lt;/p&gt;
&lt;p&gt;Slides from this talk are available here: &lt;a href=&#34;https://speakerdeck.com/aebsr/embrace-the-vertical&#34;&gt;Embrace the Vertical&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;christophe-burgmer--if-your-css-is-happy-and-you-know-it&#34;&gt;Christophe Burgmer — If your CSS is happy and you know it&amp;hellip;&lt;/h3&gt;
&lt;p&gt;This was a really interesting talk about testing your CSS visually with a tool Christophe has been developing called &lt;a href=&#34;http://cburgmer.github.io/csscritic/&#34;&gt;CSS Critic&lt;/a&gt;. Christophe covered some of the existing CSS/HTML testing tools like &lt;a href=&#34;http://docs.seleniumhq.org/&#34;&gt;Selenium&lt;/a&gt; and found that while they worked well they didn’t meet his needs entirely. He wanted a way to visually diff the changes that were made and to be able to write tests for his UI code. For example, when the “accepted” version of the page changed visually, he wanted to be notified and decide whether or not to accept the proposed change.&lt;/p&gt;
&lt;p&gt;Christophe demoed the tool for us and it was really cool to see a visual diff in the browser. For a change that was introduced, screenshots of the old, new and difference were displayed. The user then has the ability to accept / OK the change or reject it. You can view the tool in action on the &lt;a href=&#34;http://cburgmer.github.io/csscritic/&#34;&gt;CSS Critic&lt;/a&gt; site. Under the hood, CSS Critic uses some other nifty projects including &lt;a href=&#34;https://github.com/BBC-News/wraith&#34;&gt;Wraith&lt;/a&gt;, &lt;a href=&#34;https://github.com/Huddle/PhantomCSS&#34;&gt;PhantomCSS&lt;/a&gt;, &lt;a href=&#34;http://casperjs.org/&#34;&gt;CasperJS&lt;/a&gt; and &lt;a href=&#34;https://web.archive.org/web/20160728015647/http://hardy.io/&#34;&gt;Hardy&lt;/a&gt;. Christophe also mentioned &lt;a href=&#34;http://csste.st/&#34;&gt;csste.st&lt;/a&gt; as a site which curates information on all of these topics and projects.&lt;/p&gt;
&lt;p&gt;Slides from this talk are available here: &lt;a href=&#34;http://cburgmer.github.io/csscritic/cssconf2014/#/step-1&#34;&gt;If you CSS is happy and you know it&amp;hellip;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;zach-leatherman--bulletproof-icon-fonts&#34;&gt;Zach Leatherman — Bulletproof Icon Fonts&lt;/h3&gt;
&lt;p&gt;Zach wrote a &lt;a href=&#34;https://filamentgroup.com/lab/bulletproof_icon_fonts.html&#34;&gt;great article&lt;/a&gt; on Bulletproof Accessible Icon Fonts earlier this year and his talk was along similar lines. He chronicled some of the challenges and pitfalls worth knowing about in order to support icon fonts in your sites and applications. Browser support varies a great deal and Zach cited John Holt Ripley’s &lt;a href=&#34;https://web.archive.org/web/20161125011236/http://unicode.johnholtripley.co.uk:80/all/&#34;&gt;Unify&lt;/a&gt; unicode support charts as a helpful reference. He works on the &lt;a href=&#34;https://github.com/filamentgroup/a-font-garde&#34;&gt;a-font-garde&lt;/a&gt; project which documents best (er. bulletproof) practices for working with icon fonts today.&lt;/p&gt;
&lt;h3 id=&#34;stay-tuned&#34;&gt;Stay Tuned&lt;/h3&gt;
&lt;p&gt;Watch for one more post later this week with the last batch of talks from the conf!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Creating custom button graphics in Android</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/06/creating-custom-button-graphics-in/"/>
      <id>https://www.endpointdev.com/blog/2013/06/creating-custom-button-graphics-in/</id>
      <published>2013-06-07T00:00:00+00:00</published>
      <author>
        <name>Zed Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;In the Android timesheet app I’m working on, I have a scrollable layout of RadioButtons for the user to pick how much time they’ve spent on a project (see &lt;a href=&#34;/blog/2013/05/dynamically-adding-custom-radio-buttons/&#34;&gt;my earlier blog post about it&lt;/a&gt;), and for that I use custom button graphics to make it look nice. So, I’m going to show you how to do that with 9-patch PNGs and selector XML.&lt;/p&gt;
&lt;p&gt;First, what’s a 9-patch PNG? A 9-patch is a special PNG image where you specify regions that can be stretched to make room for text. Android will automatically resize a 9-patch to best fit whatever contents you give it. The tool you need to create a 9-patch image is included in the Android SDK Tools, so download that if you haven’t already.&lt;/p&gt;
&lt;p&gt;More information about 9-patch images can be found &lt;a href=&#34;https://developer.android.com/guide/topics/graphics/drawables#nine-patch&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Okay! I’ve got custom button graphics (72x72 for HDPI screens), drawn in the Gimp and saved in my project’s res/drawable-hdpi/ folder as button_selected.png and button_unselected.png:&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;
&lt;a href=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-0-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-0.png&#34;/&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-1-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-1.png&#34;/&gt;&lt;/a&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;To convert it to a 9-patch, browse to the tools/ directory of the Android SDK and run draw9patch. This will open a window with a graphical editor on the left, and a button preview on the right. The editor window is for specifying which parts of the image will be stretched. The top and left edges show this, and the right and bottom edges show which parts of the image can contain the text you put in the button.&lt;/p&gt;
&lt;p&gt;When you’ve finished with draw9patch, save the images in the same place, but with .9.png as the file extension (in this case, res/drawable-hdpi/button_selected.png will be res/drawable-hdpi/button_selected.9.png). Make sure to delete the old images, because Android R (the generated resource class) doesn’t use file extensions, so it can’t tell the difference between our two image types.&lt;/p&gt;
&lt;p&gt;Now, let’s try making a button with our custom graphics. Add a Button to your Activity XML, like so:&lt;/p&gt;
&lt;p&gt;res/layout/activity_main.xml&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;RelativeLayout xmlns:android=&amp;#34;http://schemas.android.com/apk/res/android&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   xmlns:tools=&amp;#34;http://schemas.android.com/tools&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:layout_width=&amp;#34;match_parent&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:layout_height=&amp;#34;match_parent&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:paddingBottom=&amp;#34;@dimen/activity_vertical_margin&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:paddingLeft=&amp;#34;@dimen/activity_horizontal_margin&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:paddingRight=&amp;#34;@dimen/activity_horizontal_margin&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:paddingTop=&amp;#34;@dimen/activity_vertical_margin&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   tools:context=&amp;#34;.MainActivity&amp;#34; &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;Button
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:id=&amp;#34;@+id/button1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:layout_width=&amp;#34;wrap_content&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:layout_height=&amp;#34;wrap_content&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:background=&amp;#34;@drawable/button_selected&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:onClick=&amp;#34;doStuff&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:text=&amp;#34;@string/hello_world&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;/RelativeLayout&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, when we run it, it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-2-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-2.png&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So we have our custom background working, but it’s the same (red) whether or not you’re pushing it. To use different images for different states, we can use selector XML. With ours, we just have two images, so it’s simple:&lt;/p&gt;
&lt;p&gt;res/drawable/button_selector.xml&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;selector xmlns:android=&amp;#34;http://schemas.android.com/apk/res/android&amp;#34; &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;!-- When selected, use this image --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;item android:drawable=&amp;#34;@drawable/button_selected&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:state_pressed=&amp;#34;true&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;!-- When not selected, use this image --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;item android:drawable=&amp;#34;@drawable/button_unselected&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     android:state_pressed=&amp;#34;false&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;/selector&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, instead of pointing our Button at an image directly, we can reference the XML instead:&lt;/p&gt;
&lt;p&gt;res/layout/activity_main.xml&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;font color=&amp;#34;#969696&amp;#34;&amp;gt;android:layout_height=&amp;#34;wrap_content&amp;#34;&amp;lt;/font&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; android:background=&amp;#34;@drawable/button_selector&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &amp;lt;font color=&amp;#34;#969696&amp;#34;&amp;gt;android:onClick=&amp;#34;doStuff&amp;#34;&amp;lt;/font&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And when we run it, it looks great (assuming you like bright red)!&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;
&lt;a href=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-3-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-3.png&#34;/&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;a href=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-4-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/06/creating-custom-button-graphics-in/image-4.png&#34;/&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;This project is on &lt;a href=&#34;https://github.com/obnoxiousorc/com.example.custombuttonsdemo&#34;&gt;GitHub,&lt;/a&gt; if you’d like to download it and try some stuff.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Dynamically adding custom radio buttons in Android</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/05/dynamically-adding-custom-radio-buttons/"/>
      <id>https://www.endpointdev.com/blog/2013/05/dynamically-adding-custom-radio-buttons/</id>
      <published>2013-05-09T00:00:00+00:00</published>
      <author>
        <name>Zed Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;I’ve been writing a timesheet tracking app for End Point. In working on various features of this app, I’ve had more than a few problems to work through, since this project is one of my first on Android and I’ve never used many of the Android features that I’m now using. One particularly fun bit was setting up a scrollable list of radio buttons with numbers from 0 – 23 (no, we don’t often have people working 23 hours on a project in a day, but just in case!) when the user is creating an entry, as a prettier and more backward-compatible alternative to using a number picker to indicate how many hours were spent on this particular job.&lt;/p&gt;
&lt;p&gt;In Android, view layouts are usually defined in XML like this:&lt;/p&gt;
&lt;p&gt;layout/activity_hour_picker.xml&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;RelativeLayout xmlns:android=&amp;#34;http://schemas.android.com/apk/res/android&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  android:layout_width=&amp;#34;match_parent&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  android:layout_height=&amp;#34;match_parent&amp;#34; &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;Button android:id=&amp;#34;@+id/button&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    android:layout_height=&amp;#34;wrap_content&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    android:layout_width=&amp;#34;wrap_content&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    android:onClick=&amp;#34;doStuff&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    android:text=&amp;#34;Hello World!&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/RelativeLayout&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Simple. But, as you can imagine, not so fun when you’re adding a list of 24 buttons—​so, I decided to add them dynamically in the code. First, though, a ScrollView and RadioGroup (ScrollView only allows one child) need to be defined in the XML, no point in doing that programmatically. Let’s add those:&lt;/p&gt;
&lt;p&gt;layout/activity_hour_picker.xml&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;RelativeLayout xmlns:android=&amp;#34;http://schemas.android.com/apk/res/android&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:layout_width=&amp;#34;match_parent&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   android:layout_height=&amp;#34;match_parent&amp;#34; &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;lt;HorizontalScrollView
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        android:id=&amp;#34;@+id/hour_scroll_view&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        android:layout_width=&amp;#34;match_parent&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        android:layout_height=&amp;#34;wrap_content&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        android:fillViewport=&amp;#34;true&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        android:scrollbars=&amp;#34;none&amp;#34; &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;RadioGroup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            android:id=&amp;#34;@+id/hour_radio_group&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            android:layout_width=&amp;#34;wrap_content&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            android:layout_height=&amp;#34;match_parent&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            android:orientation=&amp;#34;horizontal&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            // This is where our buttons will be
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;/RadioGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/HorizontalScrollView&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/RelativeLayout&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Okay. So, now, in our Activity, we need to override onCreate if we haven’t already and add the following code:&lt;/p&gt;
&lt;p&gt;src/com/example/HourPickerActivity.java&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@Override
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;public void onCreate(Bundle icicle) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  super.onCreate(icicle);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  setContentView(R.layout.activity_hour_picker);  // This adds the views from the XML we wrote earlier
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ViewGroup hourButtonLayout = (ViewGroup) findViewById(R.id.hour_radio_group);  // This is the id of the RadioGroup we defined
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  for (int i = 0; i &amp;lt; RANGE_HOURS; i++) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    RadioButton button = new RadioButton(this);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    button.setId(i);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    button.setText(Integer.toString(i));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    button.setChecked(i == currentHours); // Only select button with same index as currently selected number of hours
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    button.setBackgroundResource(R.drawable.item_selector); // This is a custom button drawable, defined in XML
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    hourButtonLayout.addView(button);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And this is what we get:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2013/05/dynamically-adding-custom-radio-buttons/image-0-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/05/dynamically-adding-custom-radio-buttons/image-0.png&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It scrolls horizontally like we want, but there’s a problem—​we don’t want the default radio button selector showing up, since we’ve already got custom button graphics showing. And we can’t forget that we’re still missing the code to make everything within the RadioGroup work properly—​In other words, the buttons won’t do anything when a user clicks them. So, let’s add a listener to each button as it’s created:&lt;/p&gt;
&lt;p&gt;src/com/example/HourPickerActivity.java&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;font color=&amp;#34;#969696&amp;#34;&amp;gt;button.setId(i);&amp;lt;/font&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;button.setBackgroundResource(R.drawable.item_selector); // This is a custom button drawable, defined in XML
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;button.setOnClickListener(new OnClickListener() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        @Override
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        public void onClick(View view) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ((RadioGroup) view.getParent()).check(view.getId());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            currentHours = view.getId();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;font color=&amp;#34;#969696&amp;#34;&amp;gt;button.setText(Integer.toString(i));&amp;lt;/font&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So now the currently selected value is ready in our static variable currentHours for whenever the user is finished. Now we need to get rid of the standard radio button graphics. The solution I found is to use selector XML, with just one item that points to a transparent drawable:&lt;/p&gt;
&lt;p&gt;drawable/null_selector.xml&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;selector xmlns:android=&amp;#34;http://schemas.android.com/apk/res/android&amp;#34; &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;item android:drawable=&amp;#34;@android:color/transparent&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/selector&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Set each button to use it (and to center the text) like this (R.drawable.null_selector is our selector XML):&lt;/p&gt;
&lt;p&gt;src/com/example/HourPickerActivity.java&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;font color=&amp;#34;#969696&amp;#34;&amp;gt;button.setText(Integer.toString(i));&amp;lt;/font&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;button.setGravity(Gravity.CENTER);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;button.setButtonDrawable(R.drawable.null_selector);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;font color=&amp;#34;#969696&amp;#34;&amp;gt;button.setChecked(i == currentHours); // Only select button with same index as currently selected number of hours  &amp;lt;/font&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, let’s see how this all has pulled together.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2013/05/dynamically-adding-custom-radio-buttons/image-1-big.png&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2013/05/dynamically-adding-custom-radio-buttons/image-1.png&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There—​that looks much better! And it works great.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>API gaps: an Android MediaPlayer example</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2011/03/api-gaps-android-mediaplayer-example/"/>
      <id>https://www.endpointdev.com/blog/2011/03/api-gaps-android-mediaplayer-example/</id>
      <published>2011-03-02T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;Many programming library APIs come with several levels of functionality, including the low-level but flexible way, and the high-level and simpler but limited way. I recently came across a textbook case of this in Android’s Java audio API, in the &lt;a href=&#34;https://developer.android.com/reference/android/media/MediaPlayer.html&#34;&gt;MediaPlayer class&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We needed to play one of several custom Ogg Vorbis audio files in the &lt;a href=&#34;https://web.archive.org/web/20110902084453/http://www.locateexpress.com/&#34;&gt;Locate Express&lt;/a&gt; Android app to alert the user to various situations.&lt;/p&gt;
&lt;p&gt;Getting this going initially was fairly straightforward:&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/850599.js?file=PlaySound.java&#34;&gt;&lt;/script&gt;
&lt;p&gt;In this simplified version of our PlaySound class we pass in the app resource ID of the sound file, and using the &lt;a href=&#34;https://developer.android.com/reference/android/media/MediaPlayer#create(android.content.Context,%20int)&#34;&gt;MediaPlayer.create()&lt;/a&gt; method is about as simple as can be.&lt;/p&gt;
&lt;p&gt;We keep a map of playing sound files so that external events can stop all playing sounds at once in a single call.&lt;/p&gt;
&lt;p&gt;We set an OnCompletionListener to clean up after ourselves if the sound plays to its end without interruption.&lt;/p&gt;
&lt;p&gt;Everything worked fine. Except for a pesky volume problem in real-world use. MediaPlayer uses Android’s default audio stream, which seemed to be &lt;a href=&#34;https://developer.android.com/reference/android/media/AudioManager#STREAM_MUSIC&#34;&gt;STREAM_MUSIC&lt;/a&gt;. That plays the audio files fine, but has an interesting consequence during the actual playing: You can’t turn the volume down or up because the volume control outside of any specific media-playing contexts affects the &lt;a href=&#34;https://developer.android.com/reference/android/media/AudioManager#STREAM_RING&#34;&gt;STREAM_RING&lt;/a&gt; volume, not the one we’re playing on. Practically speaking, that’s a big problem because if the music stream was turned up all the way and the alert goes off at full volume in a public place, you have no way to turn it down! (Not a hypothetical situation, as you may guess &amp;hellip;)&lt;/p&gt;
&lt;p&gt;Switching to STREAM_RING would be the obvious and hopefully simple thing to do, but calling the &lt;a href=&#34;https://developer.android.com/reference/android/media/MediaPlayer.html#setAudioStreamType(int)&#34;&gt;MediaPlayer.setAudioStreamType() method&lt;/a&gt; must be done before the MediaPlayer state machine enters the prepared state, which the MediaPlayer.create() does automatically. The convenience is our undoing!&lt;/p&gt;
&lt;p&gt;Switching over to the low-level way of doing things turns out to be a bit of a pain because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;There’s no interface to pass an Android resource ID to one of the &lt;a href=&#34;https://developer.android.com/reference/android/media/MediaPlayer.html#setDataSource(java.lang.String)&#34;&gt;setDataSource()&lt;/a&gt; methods. Instead, we have to use a file path, file descriptor, or URI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The easiest of those options seemed to be a URI, and doing a little research, the format comes to light: android.resource://package.name/resource_id&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We have to handle IOException which wasn’t throwable using the higher-level MediaPlayer.create() invocation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Putting it all together, we end up with:&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/850599.js?file=PlaySound2.java&#34;&gt;&lt;/script&gt;
&lt;p&gt;It’s not so hard, but it’s not the one-line addition of MediaPlayer.setAudioStreamType() that I expected. This is an example of how the API lacking a MediaPlayer.setDataSource(Context, int) for the resource ID makes a simple change a lot more painful than it really needs to be—​especially since the URI variation could easily be handled behind the scenes by MediaPlayer.&lt;/p&gt;
&lt;p&gt;I later took a look at the &lt;a href=&#34;https://github.com/aosp-mirror/platform_frameworks_base/blob/gingerbread/media/java/android/media/MediaPlayer.java#L648&#34;&gt;Android MediaPlayer class source&lt;/a&gt; to see how the create() method does its work:&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/850599.js?file=MediaPlayer-excerpt.java&#34;&gt;&lt;/script&gt;
&lt;p&gt;Instead of creating a URI, the authors chose to go the file descriptor route, and they check for exceptions just like I had to. It seems more cumbersome to have to open the file, get the descriptor, and manually call getStartOffset() and getLength() in the call to setDataSource, but perhaps there’s some benefit there.&lt;/p&gt;
&lt;p&gt;This gap between low-level and high-level interfaces is another small lesson I’ll remember both when using and creating APIs.&lt;/p&gt;
&lt;p&gt;There’s one final unanswered question I had earlier: Was STREAM_MUSIC really the default output stream? Empirically that seemed to be the case, but I didn’t see it stated explicitly anywhere in the documentation. To find out for sure we have to delve into the native C++ code that backs MediaPlayer, in &lt;a href=&#34;https://github.com/aosp-mirror/platform_frameworks_base/blob/gingerbread/media/libmedia/mediaplayer.cpp#L48&#34;&gt;libmedia/mediaplayer.cpp&lt;/a&gt;, and sure enough, in the constructor the default is set:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mStreamType = AudioSystem::MUSIC;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;My experience with Android so far has been that it’s well documented, but it’s been very nice to be able to read the source and see how the core libraries are implemented when needed.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>DROID 2 review</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2011/01/droid-2-review/"/>
      <id>https://www.endpointdev.com/blog/2011/01/droid-2-review/</id>
      <published>2011-01-24T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;I got a &lt;a href=&#34;https://web.archive.org/web/20110221030520/http://www.motorola.com/Consumers/US-EN/Consumer-Product-and-Services/Mobile-Phones/Motorola-DROID-2-US-EN&#34;&gt;Motorola DROID 2&lt;/a&gt; phone a couple of months ago and have assembled here my notes about how it has worked out so far. First, some background.&lt;/p&gt;
&lt;p&gt;This is my second Android phone. My first was the &lt;a href=&#34;https://web.archive.org/web/20110221122921/http://developer.htc.com/google-io-device.html&#34;&gt;Google Ion&lt;/a&gt;, basically the same as the HTC Magic. That was running standard Android 1.5 (Cupcake), while the DROID 2 runs Android 2.2 (Froyo) tweaked somewhat by Motorola. I’ve used several other Android phones belonging to friends and relatives.&lt;/p&gt;
&lt;p&gt;Overall I like the Android operating system fairly well. Like everything, it can be improved. It’s been advancing at a fairly quick pace. It’s mostly free software. Too many phones are locked down and have to be broken into to change the operating system, but Android’s still a freer computing environment than most phones have and I hope the situation will improve over time.&lt;/p&gt;
&lt;p&gt;I take for granted much of Android’s feature set: The excellent system-wide notification bar that many apps hook into and which is always easy to get to. The solid multitasking. Automatic screen adjustment for using the phone in landscape vs. portrait mode. The ability to mount the normal filesystem on the SD card from another computer via USB or by removing the SD card. The freedom to run any applications I want, downloaded from wherever, not just the Android Market.&lt;/p&gt;
&lt;p&gt;For one of our clients, I’m currently developing an Android application in Java that uses geolocation, JSON web services, and Urban Airship’s &lt;a href=&#34;https://www.urbanairship.com/products/mobile-app-engagement&#34;&gt;AirMail Push&lt;/a&gt; for push notifications. So I’m using the phone both as my main mobile phone and as an app developer.&lt;/p&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;p&gt;This is the first phone I’ve had with a full slide-out QWERTY keyboard. I wasn’t sure if I’d use it often or just stick with the touchscreen keyboard. So far I use the real keyboard most of the time. But it’s nice to be able to use either the hard keyboard or touchscreen keyboard, whichever’s more convenient.&lt;/p&gt;
&lt;p&gt;I like the keyboard a lot, especially that its rows are offset like a normal keyboard and not a straight aligned grid:&lt;/p&gt;
&lt;img width=&#34;376&#34; height=&#34;369&#34; src=&#34;/blog/2011/01/droid-2-review/Droid2_FrontOpen_email_alt.png&#34;&gt;
&lt;p&gt;The real keyboard makes using ssh (via the ConnectBot app) on the phone much easier than with the touchscreen keyboard, because the real keyboard doesn’t use up any screen real estate.&lt;/p&gt;
&lt;p&gt;The biggest annoyance for me has been the keyboard’s microphone key which launches the voice commands function. I don’t use voice commands, so this has never been what I’ve wanted and is highly annoying when accidentally pressed.&lt;/p&gt;
&lt;p&gt;Third-party alternative touchscreen keyboards for European languages, Hebrew, Arabic, and others work pretty well too.&lt;/p&gt;
&lt;h3 id=&#34;touchscreen&#34;&gt;Touchscreen&lt;/h3&gt;
&lt;p&gt;I really miss having physical buttons for menu, home, back, and search, as I had on the HTC Magic:&lt;/p&gt;
&lt;img width=&#34;180&#34; height=&#34;360&#34; src=&#34;/blog/2011/01/droid-2-review/htc-magic-google-ion-vertical_180x360.png&#34;&gt;
&lt;p&gt;On the DROID 2 they’re touchscreen buttons and it’s annoying and error-prone to have to look at the phone and carefully press one. Much better to have real buttons for those core functions used by all apps.&lt;/p&gt;
&lt;p&gt;However, good riddance to the trackball.&lt;/p&gt;
&lt;p&gt;The DROID 2 screen itself is higher resolution, bright, and responds well to touch, swipe, and multitouch, as you’d expect.&lt;/p&gt;
&lt;h3 id=&#34;battery&#34;&gt;Battery&lt;/h3&gt;
&lt;p&gt;Battery life on the DROID 2 is not great, especially when using GPS heavily. But that’s a problem on most phones.&lt;/p&gt;
&lt;p&gt;I later got the optional extended-life battery. It isn’t much bigger or heavier, and does add a lot more life to the phone. It wouldn’t be necessary if I weren’t doing heavy GPS &amp;amp; web service interaction with my own Android app under development.&lt;/p&gt;
&lt;h3 id=&#34;audio&#34;&gt;Audio&lt;/h3&gt;
&lt;p&gt;The DROID 2 has a normal 1/8&amp;quot; stereo headphone jack, which is nice. The HTC Magic needed a special adapter to use normal headphones, which was a pain.&lt;/p&gt;
&lt;p&gt;Audio playback is usually fine, though on a couple of occasions it’s stuttered, presumably while contending with other apps for CPU. Audio playback when the screen is off doesn’t drain the battery much at all.&lt;/p&gt;
&lt;h3 id=&#34;calling-and-contacts&#34;&gt;Calling and Contacts&lt;/h3&gt;
&lt;p&gt;There have been a few isolated incidents, perhaps involving switching off Bluetooth, where I couldn’t turn down the call volume in the middle of the call. That could’ve been either a generic Android problem, or the Motorola Android build; I’m not sure.&lt;/p&gt;
&lt;p&gt;I didn’t much like the special Motorola apps that shipped on the phone, but simply not using them has made them no problem.&lt;/p&gt;
&lt;p&gt;An exception to that is the Contacts app that integrates with Facebook and Twitter. That’s a lot more helpful than I imagined it would be, though when you have many contacts it can sometime slow down on opening an individual contact, when I imagine it tries to fetch the latest details from social networks.&lt;/p&gt;
&lt;p&gt;The in-call screen’s “Mute” button is small and way too close to the “End call” button:&lt;/p&gt;
&lt;img border=&#34;0&#34; src=&#34;/blog/2011/01/droid-2-review/image-0.png&#34; style=&#34;width: 225px; height: 400px;&#34;&gt;
&lt;p&gt;That’s led to me accidentally hanging up during conference calls a couple of times. The stock open source Android call screen is arguably less pretty, but equally-sized buttons makes it easier to use:&lt;/p&gt;
&lt;img border=&#34;0&#34; src=&#34;/blog/2011/01/droid-2-review/image-1.png&#34; style=&#34;width: 270px; height: 400px;&#34;&gt;
&lt;p&gt;Because I’m using Verizon for the first time with this phone, I’ve been introduced to the sad problem of not being able to use voice and data at the same time on CDMA. I used to laugh about that when I was using AT&amp;amp;T because it seemed silly. Now I find it really does get in the way fairly often.&lt;/p&gt;
&lt;p&gt;The normal Skype app doesn’t allow using a Bluetooth headset or using the cell network for calls (in the US). The special Verizon Skype app burns voice minutes and won’t let you use wifi for calling (in the US). The combination of proprietary software plus controlling phone carriers is bad for users! Because of the limitations I use Skype mostly for text chat on my phone.&lt;/p&gt;
&lt;h3 id=&#34;gps&#34;&gt;GPS&lt;/h3&gt;
&lt;p&gt;As I mentioned, the GPS uses a lot of battery. But it works well. The GPS on my HTC Magic sometimes took a very long time to get a location fix, but on the DROID 2 it’s fast and works very well. Given the location-aware app I’m working on, I’ve had more opportunity to notice this than I otherwise might have.&lt;/p&gt;
&lt;p&gt;There was one funny problem with the GPS, though. Once when the battery got down to 5% or so, the GPS started to report hilariously wrong data to my app, and thus to the web service it reported to. For example, it reported every second that my phone, which was sitting on my nightstand, was traveling at 121 m/s (270 mph), and the point in latitude and longitude moved quickly. This continued even after I’d plugged the phone back in. I haven’t seen it happen since, but it was strange.&lt;/p&gt;
&lt;h3 id=&#34;useful-apps&#34;&gt;Useful apps&lt;/h3&gt;
&lt;p&gt;I don’t spend a lot of time trying out new apps, but I’ve found several to be very useful: K-9 Mail for multiple mail accounts (reviewed yesterday on &lt;a href=&#34;https://arstechnica.com/information-technology/2011/01/excellent-k-9-mail-app-for-android-keeps-your-messages-on-a-leash/&#34;&gt;Ars Technica&lt;/a&gt;), Yaaic (an IRC client), 3G Watchdog (bandwidth monitor), TweetsRide (Twitter client), Google Sky Map, and My Tracks. I haven’t found a great music player yet, but the built-in Android Music app and Winamp are both fine.&lt;/p&gt;
&lt;h3 id=&#34;security&#34;&gt;Security&lt;/h3&gt;
&lt;p&gt;I’ve long used crypto filesystems on desktop and laptop computers, and feel the lack on Android. A phone needs a solid cryptofs option more than normal computers even, and I hope that’ll be available soon.&lt;/p&gt;
&lt;h3 id=&#34;the-end-for-now&#34;&gt;The end (for now)&lt;/h3&gt;
&lt;p&gt;I’m probably forgetting lots of things, but this is already longer than I’d expected, so I’ll stop now and put any afterthoughts in the comments. In short, I like the phone, but it’s hard to avoid looking forward to a brighter future when it comes to mobile devices.&lt;/p&gt;

      </content>
    </entry>
  
</feed>
