├── .gitignore ├── 404.html ├── CNAME ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── _config.yml ├── _drafts └── 2015-04-01-shared-element-callbacks.md ├── _includes ├── fonts.css ├── normalize.css ├── posts │ └── 2016 │ │ └── 11 │ │ └── 29 │ │ ├── includes10_downloading_animated_svgs.html │ │ ├── includes1_drawing_paths_path_commands.html │ │ ├── includes2_transforming_paths_demo.html │ │ ├── includes3_transforming_paths_animated_svgs.html │ │ ├── includes4_transforming_paths_indeterminate_progress.html │ │ ├── includes5_trimming_stroked_paths_demo.html │ │ ├── includes6_trimming_stroked_paths_animated_svgs.html │ │ ├── includes7_trimming_stroked_paths_indeterminate_progress.html │ │ ├── includes8_morphing_paths_animated_svgs.html │ │ └── includes9_clipping_paths_animated_svgs.html ├── social.css ├── style.css ├── style.min.css └── syntax.css ├── _js ├── build │ └── .gitignore └── src │ ├── material.min.js │ ├── material.min.js.map │ └── posts │ └── 2016 │ └── 11 │ └── 29 │ └── animated-icon-demos.js ├── _layouts ├── default.html └── post.html ├── _less └── style.less ├── _posts ├── 2012-05-21-correctly-managing-your-sqlite-database.md ├── 2012-05-24-using-newinstance-to-instantiate-a-fragment.md ├── 2012-05-26-reaping-benefits-of-the-loadermanager.md ├── 2012-05-30-basic-android-debugging-with-logs.md ├── 2012-06-04-ensuring-compatibility-with-utility-class.md ├── 2012-06-13-designing-for-backwards-compatibility.md ├── 2012-06-18-why-ice-cream-sandwich-crashes-your-app.md ├── 2012-06-25-content-providers-and-content-resolvers.md ├── 2012-07-06-loaders-part1.md ├── 2012-07-22-loaders-part2.md ├── 2012-08-07-exit-application-dialogs-are-evil.md ├── 2012-08-21-loaders-part3.md ├── 2012-08-26-follow-this-blog-on-google-currents.md ├── 2012-09-16-loaders-part4.md ├── 2012-10-11-sqlite-content-providers-thread-safety.md ├── 2013-01-08-google-play-services-setup-verification.md ├── 2013-01-12-use-go-to-implement-android-backends.md ├── 2013-01-14-how-to-leak-a-context-handlers-inner-classes.md ├── 2013-04-15-activitys-threads-and-memory-leaks.md ├── 2013-04-29-handling-config-changes-with-fragments.md ├── 2013-07-31-binders-and-window-tokens.md ├── 2013-08-05-binders-and-death-recipients.md ├── 2013-08-20-fragment-transactions-activity-state-loss.md ├── 2014-01-08-redesigning-android-design-patterns.md ├── 2014-01-13-thread-scheduling-in-android.md ├── 2014-12-04-activity-fragment-transitions-in-android-lollipop-part1.md ├── 2014-12-15-activity-fragment-content-transitions-in-depth-part2.md ├── 2015-01-12-activity-fragment-shared-element-transitions-part3a.md ├── 2015-03-09-activity-postponed-shared-element-transitions-part3b.md ├── 2016-08-07-colors-drawables-theme-attributes.md ├── 2016-08-11-theming-buttons-with-themeoverlays-background-tints.md ├── 2016-11-29-introduction-to-icon-animation-techniques.md ├── 2018-01-24-experimenting-with-nested-scrolling.md ├── 2018-03-05-introducing-kyrie-animated-vector-drawables.md └── 2018-11-13-android-studio-svg-to-vector-cli.md ├── about └── index.html ├── adp.sublime-project ├── archives └── index.html ├── assets ├── fonts │ ├── social-media.eot │ ├── social-media.svg │ ├── social-media.ttf │ └── social-media.woff ├── images │ ├── 2048_icon_72x72.png │ ├── android1x.png │ ├── android2x.png │ ├── favicon.psd │ ├── favicon1024.png │ ├── favicon128.png │ ├── favicon16.png │ ├── favicon256.png │ ├── favicon32.png │ ├── favicon512.png │ ├── favicon64.png │ ├── ic_launcher.png │ ├── nexus6_frame.png │ ├── posts │ │ ├── 2012 │ │ │ ├── 08 │ │ │ │ └── 07 │ │ │ │ │ ├── back-button-pressed.png │ │ │ │ │ └── dialog-showing.png │ │ │ └── 09 │ │ │ │ └── 16 │ │ │ │ ├── app-screenshot.png │ │ │ │ ├── eclipse-screenshot.png │ │ │ │ └── google-play-badge.png │ │ ├── 2013 │ │ │ └── 04 │ │ │ │ ├── 15 │ │ │ │ ├── activity-leak.png │ │ │ │ └── leaky-threads-screenshot.png │ │ │ │ └── 29 │ │ │ │ └── worker-fragments-screenshot.png │ │ ├── 2014 │ │ │ └── 01 │ │ │ │ └── 08 │ │ │ │ ├── adp-n7-screenshot-after-resized.png │ │ │ │ ├── adp-n7-screenshot-after.png │ │ │ │ ├── adp-n7-screenshot-before-resized.png │ │ │ │ └── adp-n7-screenshot-before.png │ │ ├── 2016 │ │ │ └── 08 │ │ │ │ ├── 11 │ │ │ │ ├── themed-buttons-disabled-19.png │ │ │ │ ├── themed-buttons-disabled-23.png │ │ │ │ ├── themed-buttons-enabled-pressed-19.png │ │ │ │ ├── themed-buttons-enabled-pressed-23.png │ │ │ │ ├── themed-buttons-enabled-unpressed-19.png │ │ │ │ └── themed-buttons-enabled-unpressed-23.png │ │ │ │ └── 07 │ │ │ │ ├── rant7-contextcompat-examples-19.png │ │ │ │ └── rant7-contextcompat-examples-23.png │ │ └── 2018 │ │ │ └── 01 │ │ │ └── 24 │ │ │ ├── nsv-overlay-small.png │ │ │ ├── nsv-overlay.png │ │ │ ├── rv-overlay-small.png │ │ │ ├── rv-overlay.png │ │ │ ├── sample-app-layouts.jpg │ │ │ └── sample-app-layouts.png │ ├── profile-photo-gopher.png │ ├── profile-photo.jpg │ └── shapeshifter.svg └── videos │ └── posts │ ├── 2014 │ └── 12 │ │ ├── 15 │ │ ├── games-opt.mp4 │ │ ├── games-opt.ogv │ │ ├── games-opt.png │ │ ├── games-opt.webm │ │ ├── games.mp4 │ │ ├── webview-opt.mp4 │ │ ├── webview-opt.ogv │ │ ├── webview-opt.png │ │ ├── webview-opt.webm │ │ └── webview.mp4 │ │ └── 04 │ │ ├── news-opt.mp4 │ │ ├── news-opt.ogv │ │ ├── news-opt.png │ │ ├── news-opt.webm │ │ ├── news.mp4 │ │ ├── trivial-opt.mp4 │ │ ├── trivial-opt.ogv │ │ ├── trivial-opt.png │ │ ├── trivial-opt.webm │ │ └── trivial.mp4 │ ├── 2015 │ ├── 01 │ │ └── 12 │ │ │ ├── music-opt.mp4 │ │ │ ├── music-opt.ogv │ │ │ ├── music-opt.png │ │ │ ├── music-opt.webm │ │ │ ├── music.mp4 │ │ │ ├── overlay-opt.mp4 │ │ │ ├── overlay-opt.ogv │ │ │ ├── overlay-opt.png │ │ │ ├── overlay-opt.webm │ │ │ └── overlay.mp4 │ └── 03 │ │ └── 09 │ │ ├── postpone-bug-opt.mp4 │ │ ├── postpone-bug-opt.ogv │ │ ├── postpone-bug-opt.png │ │ ├── postpone-bug-opt.webm │ │ └── postpone-bug.mp4 │ └── 2018 │ ├── 01 │ └── 24 │ │ ├── cheesesquare-opt.mp4 │ │ ├── cheesesquare-opt.ogv │ │ ├── cheesesquare-opt.webm │ │ ├── cheesesquare.m4v │ │ ├── cheesesquare.png │ │ ├── expeditions-opt.mp4 │ │ ├── expeditions-sample-opt.mp4 │ │ ├── expeditions-sample-opt.ogv │ │ ├── expeditions-sample-opt.webm │ │ ├── expeditions-sample.m4v │ │ ├── expeditions-sample.png │ │ ├── expeditions.mp4 │ │ ├── expeditions.png │ │ ├── fling-bug-opt.mp4 │ │ ├── fling-bug-orig.mp4 │ │ ├── fling-bug.mp4 │ │ ├── fling-bug.png │ │ ├── nested-scrolling-bugs1-opt.mp4 │ │ ├── nested-scrolling-bugs1-opt.ogv │ │ ├── nested-scrolling-bugs1-opt.webm │ │ ├── nested-scrolling-bugs1.m4v │ │ ├── nested-scrolling-bugs1.png │ │ ├── nested-scrolling-bugs2-opt.mp4 │ │ ├── nested-scrolling-bugs2-opt.ogv │ │ ├── nested-scrolling-bugs2-opt.webm │ │ ├── nested-scrolling-bugs2.m4v │ │ ├── nested-scrolling-bugs2.png │ │ ├── poster-cheesesquare.jpg │ │ ├── poster-cheesesquare.png │ │ ├── poster-expeditions-sample.jpg │ │ ├── poster-expeditions-sample.png │ │ ├── poster-nested-scrolling-bugs1.jpg │ │ ├── poster-nested-scrolling-bugs1.png │ │ ├── poster-nested-scrolling-bugs2.jpg │ │ ├── poster-nested-scrolling-bugs2.png │ │ ├── sample-app-fling-actual-opt.mp4 │ │ ├── sample-app-fling-actual.mp4 │ │ ├── sample-app-fling-expected-opt.mp4 │ │ ├── sample-app-fling-expected.mp4 │ │ ├── sample-app-opt.mp4 │ │ ├── sample-app-orig.mp4 │ │ ├── sample-app.mp4 │ │ ├── scroll-bug-opt.mp4 │ │ ├── scroll-bug-orig.mp4 │ │ └── scroll-bug.mp4 │ └── 03 │ └── 06 │ ├── introducing-kyrie-animals.mp4 │ ├── introducing-kyrie-animals.webm │ ├── introducing-kyrie-heartbreak.mp4 │ ├── introducing-kyrie-heartbreak.webm │ ├── introducing-kyrie-polygons.mp4 │ ├── introducing-kyrie-polygons.webm │ ├── poster-introducing-kyrie-animals.jpg │ ├── poster-introducing-kyrie-animals.png │ ├── poster-introducing-kyrie-heartbreak.jpg │ ├── poster-introducing-kyrie-heartbreak.png │ ├── poster-introducing-kyrie-polygons.jpg │ └── poster-introducing-kyrie-polygons.png ├── css ├── README.txt ├── all.css ├── material.min.css ├── material.min.css.map └── posts │ └── 2016 │ └── 11 │ └── 29 │ └── style.css ├── favicon.ico ├── feed.atom ├── gulpfile.js ├── index.html ├── package-lock.json ├── package.json ├── robots.txt ├── scripts ├── all.min.js └── disqus-lazy-load.js └── sitemap.xml /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .DS_Store 3 | *~ 4 | .jekyll-metadata 5 | node_modules 6 | adp.sublime-workspace 7 | -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 'Error 404 (Not Found)!!1' 4 | --- 5 |

404. That's an error.

6 |

The requested URL was not found on this server. That's all we know.

7 | 8 | 13 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | www.androiddesignpatterns.com -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | ruby '2.4.0' 4 | 5 | group :jekyll_plugins do 6 | gem 'github-pages', '>=105' 7 | gem 'jekyll-paginate' 8 | end -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (4.2.9) 5 | i18n (~> 0.7) 6 | minitest (~> 5.1) 7 | thread_safe (~> 0.3, >= 0.3.4) 8 | tzinfo (~> 1.1) 9 | addressable (2.5.2) 10 | public_suffix (>= 2.0.2, < 4.0) 11 | coffee-script (2.4.1) 12 | coffee-script-source 13 | execjs 14 | coffee-script-source (1.11.1) 15 | colorator (1.1.0) 16 | commonmarker (0.17.7.1) 17 | ruby-enum (~> 0.5) 18 | concurrent-ruby (1.0.5) 19 | ethon (0.11.0) 20 | ffi (>= 1.3.0) 21 | execjs (2.7.0) 22 | faraday (0.13.1) 23 | multipart-post (>= 1.2, < 3) 24 | ffi (1.9.18) 25 | forwardable-extended (2.6.0) 26 | gemoji (3.0.0) 27 | github-pages (172) 28 | activesupport (= 4.2.9) 29 | github-pages-health-check (= 1.3.5) 30 | jekyll (= 3.6.2) 31 | jekyll-avatar (= 0.5.0) 32 | jekyll-coffeescript (= 1.0.2) 33 | jekyll-commonmark-ghpages (= 0.1.3) 34 | jekyll-default-layout (= 0.1.4) 35 | jekyll-feed (= 0.9.2) 36 | jekyll-gist (= 1.4.1) 37 | jekyll-github-metadata (= 2.9.3) 38 | jekyll-mentions (= 1.2.0) 39 | jekyll-optional-front-matter (= 0.3.0) 40 | jekyll-paginate (= 1.1.0) 41 | jekyll-readme-index (= 0.2.0) 42 | jekyll-redirect-from (= 0.12.1) 43 | jekyll-relative-links (= 0.5.2) 44 | jekyll-remote-theme (= 0.2.3) 45 | jekyll-sass-converter (= 1.5.0) 46 | jekyll-seo-tag (= 2.3.0) 47 | jekyll-sitemap (= 1.1.1) 48 | jekyll-swiss (= 0.4.0) 49 | jekyll-theme-architect (= 0.1.0) 50 | jekyll-theme-cayman (= 0.1.0) 51 | jekyll-theme-dinky (= 0.1.0) 52 | jekyll-theme-hacker (= 0.1.0) 53 | jekyll-theme-leap-day (= 0.1.0) 54 | jekyll-theme-merlot (= 0.1.0) 55 | jekyll-theme-midnight (= 0.1.0) 56 | jekyll-theme-minimal (= 0.1.0) 57 | jekyll-theme-modernist (= 0.1.0) 58 | jekyll-theme-primer (= 0.5.2) 59 | jekyll-theme-slate (= 0.1.0) 60 | jekyll-theme-tactile (= 0.1.0) 61 | jekyll-theme-time-machine (= 0.1.0) 62 | jekyll-titles-from-headings (= 0.5.0) 63 | jemoji (= 0.8.1) 64 | kramdown (= 1.14.0) 65 | liquid (= 4.0.0) 66 | listen (= 3.0.6) 67 | mercenary (~> 0.3) 68 | minima (= 2.1.1) 69 | rouge (= 2.2.1) 70 | terminal-table (~> 1.4) 71 | github-pages-health-check (1.3.5) 72 | addressable (~> 2.3) 73 | net-dns (~> 0.8) 74 | octokit (~> 4.0) 75 | public_suffix (~> 2.0) 76 | typhoeus (~> 0.7) 77 | html-pipeline (2.7.1) 78 | activesupport (>= 2) 79 | nokogiri (>= 1.4) 80 | i18n (0.9.1) 81 | concurrent-ruby (~> 1.0) 82 | jekyll (3.6.2) 83 | addressable (~> 2.4) 84 | colorator (~> 1.0) 85 | jekyll-sass-converter (~> 1.0) 86 | jekyll-watch (~> 1.1) 87 | kramdown (~> 1.14) 88 | liquid (~> 4.0) 89 | mercenary (~> 0.3.3) 90 | pathutil (~> 0.9) 91 | rouge (>= 1.7, < 3) 92 | safe_yaml (~> 1.0) 93 | jekyll-avatar (0.5.0) 94 | jekyll (~> 3.0) 95 | jekyll-coffeescript (1.0.2) 96 | coffee-script (~> 2.2) 97 | coffee-script-source (~> 1.11.1) 98 | jekyll-commonmark (1.1.0) 99 | commonmarker (~> 0.14) 100 | jekyll (>= 3.0, < 4.0) 101 | jekyll-commonmark-ghpages (0.1.3) 102 | commonmarker (~> 0.17.6) 103 | jekyll-commonmark (~> 1) 104 | rouge (~> 2) 105 | jekyll-default-layout (0.1.4) 106 | jekyll (~> 3.0) 107 | jekyll-feed (0.9.2) 108 | jekyll (~> 3.3) 109 | jekyll-gist (1.4.1) 110 | octokit (~> 4.2) 111 | jekyll-github-metadata (2.9.3) 112 | jekyll (~> 3.1) 113 | octokit (~> 4.0, != 4.4.0) 114 | jekyll-mentions (1.2.0) 115 | activesupport (~> 4.0) 116 | html-pipeline (~> 2.3) 117 | jekyll (~> 3.0) 118 | jekyll-optional-front-matter (0.3.0) 119 | jekyll (~> 3.0) 120 | jekyll-paginate (1.1.0) 121 | jekyll-readme-index (0.2.0) 122 | jekyll (~> 3.0) 123 | jekyll-redirect-from (0.12.1) 124 | jekyll (~> 3.3) 125 | jekyll-relative-links (0.5.2) 126 | jekyll (~> 3.3) 127 | jekyll-remote-theme (0.2.3) 128 | jekyll (~> 3.5) 129 | rubyzip (>= 1.2.1, < 3.0) 130 | typhoeus (>= 0.7, < 2.0) 131 | jekyll-sass-converter (1.5.0) 132 | sass (~> 3.4) 133 | jekyll-seo-tag (2.3.0) 134 | jekyll (~> 3.3) 135 | jekyll-sitemap (1.1.1) 136 | jekyll (~> 3.3) 137 | jekyll-swiss (0.4.0) 138 | jekyll-theme-architect (0.1.0) 139 | jekyll (~> 3.5) 140 | jekyll-seo-tag (~> 2.0) 141 | jekyll-theme-cayman (0.1.0) 142 | jekyll (~> 3.5) 143 | jekyll-seo-tag (~> 2.0) 144 | jekyll-theme-dinky (0.1.0) 145 | jekyll (~> 3.5) 146 | jekyll-seo-tag (~> 2.0) 147 | jekyll-theme-hacker (0.1.0) 148 | jekyll (~> 3.5) 149 | jekyll-seo-tag (~> 2.0) 150 | jekyll-theme-leap-day (0.1.0) 151 | jekyll (~> 3.5) 152 | jekyll-seo-tag (~> 2.0) 153 | jekyll-theme-merlot (0.1.0) 154 | jekyll (~> 3.5) 155 | jekyll-seo-tag (~> 2.0) 156 | jekyll-theme-midnight (0.1.0) 157 | jekyll (~> 3.5) 158 | jekyll-seo-tag (~> 2.0) 159 | jekyll-theme-minimal (0.1.0) 160 | jekyll (~> 3.5) 161 | jekyll-seo-tag (~> 2.0) 162 | jekyll-theme-modernist (0.1.0) 163 | jekyll (~> 3.5) 164 | jekyll-seo-tag (~> 2.0) 165 | jekyll-theme-primer (0.5.2) 166 | jekyll (~> 3.5) 167 | jekyll-github-metadata (~> 2.9) 168 | jekyll-seo-tag (~> 2.2) 169 | jekyll-theme-slate (0.1.0) 170 | jekyll (~> 3.5) 171 | jekyll-seo-tag (~> 2.0) 172 | jekyll-theme-tactile (0.1.0) 173 | jekyll (~> 3.5) 174 | jekyll-seo-tag (~> 2.0) 175 | jekyll-theme-time-machine (0.1.0) 176 | jekyll (~> 3.5) 177 | jekyll-seo-tag (~> 2.0) 178 | jekyll-titles-from-headings (0.5.0) 179 | jekyll (~> 3.3) 180 | jekyll-watch (1.5.1) 181 | listen (~> 3.0) 182 | jemoji (0.8.1) 183 | activesupport (~> 4.0, >= 4.2.9) 184 | gemoji (~> 3.0) 185 | html-pipeline (~> 2.2) 186 | jekyll (>= 3.0) 187 | kramdown (1.14.0) 188 | liquid (4.0.0) 189 | listen (3.0.6) 190 | rb-fsevent (>= 0.9.3) 191 | rb-inotify (>= 0.9.7) 192 | mercenary (0.3.6) 193 | mini_portile2 (2.3.0) 194 | minima (2.1.1) 195 | jekyll (~> 3.3) 196 | minitest (5.10.3) 197 | multipart-post (2.0.0) 198 | net-dns (0.8.0) 199 | nokogiri (1.8.1) 200 | mini_portile2 (~> 2.3.0) 201 | octokit (4.8.0) 202 | sawyer (~> 0.8.0, >= 0.5.3) 203 | pathutil (0.16.1) 204 | forwardable-extended (~> 2.6) 205 | public_suffix (2.0.5) 206 | rb-fsevent (0.10.2) 207 | rb-inotify (0.9.10) 208 | ffi (>= 0.5.0, < 2) 209 | rouge (2.2.1) 210 | ruby-enum (0.7.1) 211 | i18n 212 | rubyzip (1.2.1) 213 | safe_yaml (1.0.4) 214 | sass (3.5.4) 215 | sass-listen (~> 4.0.0) 216 | sass-listen (4.0.0) 217 | rb-fsevent (~> 0.9, >= 0.9.4) 218 | rb-inotify (~> 0.9, >= 0.9.7) 219 | sawyer (0.8.1) 220 | addressable (>= 2.3.5, < 2.6) 221 | faraday (~> 0.8, < 1.0) 222 | terminal-table (1.8.0) 223 | unicode-display_width (~> 1.1, >= 1.1.1) 224 | thread_safe (0.3.6) 225 | typhoeus (0.8.0) 226 | ethon (>= 0.8.0) 227 | tzinfo (1.2.4) 228 | thread_safe (~> 0.1) 229 | unicode-display_width (1.3.0) 230 | 231 | PLATFORMS 232 | ruby 233 | 234 | DEPENDENCIES 235 | github-pages (>= 105) 236 | jekyll-paginate 237 | 238 | RUBY VERSION 239 | ruby 2.4.0p0 240 | 241 | BUNDLED WITH 242 | 1.16.0 243 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Alex Lockwood 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Design Patterns 2 | ======================= 3 | 4 | This is the source code for www.androiddesignpatterns.com. Android Design Patterns is powered by [Jekyll](http://jekyllrb.com/) and is hosted by [GitHub Pages](http://pages.github.com/). 5 | 6 | If you wish to fork this repository to use as the basis of your own blogging template, please make sure you review the [license and copyright](#license-and-copyright) information below. 7 | 8 | ## Building locally 9 | 10 | Make sure you have at least Ruby 2.0.0 installed on your machine. From the this project's root directory, execute: 11 | 12 | ``` 13 | gem install bundler 14 | bundler install 15 | ``` 16 | 17 | Then, 18 | 19 | ``` 20 | npm install 21 | npm start 22 | ``` 23 | 24 | Then navigate to `http://localhost:4000` to view the locally built site. 25 | 26 | ## Find a typo? 27 | 28 | If you find a typo, please feel free to suggest a correction yourself by submitting a pull request! All published posts are written in standard markdown format and can be found in the [`_posts/`](/_posts) directory. If you are unfamiliar with GitHub, the easiest way to suggest a fix is to: 29 | 30 | 1. Navigate to the [`_posts/`](/_posts) directory and click on the post that contains the error. 31 | 2. Click **Edit** and update the post with your proposed correction. 32 | 3. Write a _detailed_ commit summary and description (if necessary) and click **Propose File Change**. 33 | 4. Click **Send pull request** on the following page. I'll review the correction as soon as I can! 34 | 35 | Thanks in advance! 36 | 37 | ## License and copyright 38 | 39 | All posts and images (i.e. all files in the [`_posts/`](/_posts) and [`assets/images/`](/assets/images) directories) are copyright Alex Lockwood. You may not copy, distribute, or trasmit these files or their contents without explicit permission from the author. 40 | 41 | All other files are licensed under the [MIT license](/LICENSE.txt). 42 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | name: Android Design Patterns 2 | author: 3 | name: Alex Lockwood 4 | googleplus: https://google.com/+AlexLockwood 5 | twitter: https://twitter.com/alexjlockwood 6 | stackoverflow: https://stackoverflow.com/users/844882 7 | flickr: https://www.flickr.com/photos/tailorransom 8 | github: https://github.com/alexjlockwood 9 | feedburner: https://feeds.feedburner.com/androiddesignpatterns 10 | 11 | baseurl: https://www.androiddesignpatterns.com 12 | description: 'Android Design Patterns is a website for developers who wish to better understand the Android application framework. The tutorials here emphasize proper code design and project maintainability.' 13 | 14 | paginate: 5 15 | 16 | permalink: /:year/:month/:title.html 17 | 18 | exclude: [ 19 | 'README.md', 20 | 'LICENSE.txt', 21 | 'gulpfile.js', 22 | 'node_modules', 23 | 'package.json', 24 | 'Gemfile', 25 | 'Gemfile.lock' 26 | ] 27 | plugins: [jekyll-paginate] 28 | 29 | markdown: kramdown 30 | kramdown: 31 | input: GFM 32 | syntax_highlighter: rouge 33 | hard_wrap: false 34 | 35 | host: 0.0.0.0 36 | future: true 37 | -------------------------------------------------------------------------------- /_drafts/2015-04-01-shared-element-callbacks.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Shared Element Callbacks (part 3c)' 4 | date: 2015-04-01 5 | permalink: /2015/04/shared-element-callbacks-part3c.html 6 | related: ['/2012/08/implementing-loaders.html', 7 | '/2013/08/fragment-transaction-commit-state-loss.html', 8 | '/2012/06/app-force-close-honeycomb-ics.html'] 9 | published: false 10 | --- 11 | 12 | 13 | ### Creating Advanced Transitions Using `SharedElementCallback`s 14 | 15 | You can further customize your shared element transitions by setting a [`SharedElementCallback`][SharedElementCallback]. Understanding the SharedElementCallback class will be important if you want to implement custom Transitions that are more complicated than simply moving an image from one location to another. In particular, the following three callback methods are very important to understand when writing more complex shared element transitions: 16 | 17 | * `onMapSharedElements()` lets you adjust the mapping of shared element names to Views. **TODO: more detail.** 18 | * `onSharedElementStart()` and `onSharedElementEnd()` let you adjust view properties in your layout immediately before the transitions capture their start and end values respectively. **TODO: more detail.** 19 | -------------------------------------------------------------------------------- /_includes/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:'Asap';font-style:normal;font-weight:400;src:local('Asap'),local('Asap-Regular'),url(https://themes.googleusercontent.com/static/fonts/asap/v1/nvGvEwsMAvkIa6w2U-PXffesZW2xOQ-xsNqO47m55DA.woff) format('woff')}@font-face{font-family:'Asap';font-style:normal;font-weight:700;src:local('Asap Bold'),local('Asap-Bold'),url(https://themes.googleusercontent.com/static/fonts/asap/v1/QGN0GG0540fyG6NL_PpOpgLUuEpTyoUstqEm5AMlJo4.woff) format('woff')}@font-face{font-family:'Asap';font-style:italic;font-weight:400;src:local('Asap Italic'),local('Asap-Italic'),url(https://themes.googleusercontent.com/static/fonts/asap/v1/QnZU7dKCBgkkMBlac2djsOvvDin1pK8aKteLpeZ5c0A.woff) format('woff')}@font-face{font-family:'Asap';font-style:italic;font-weight:700;src:local('Asap Bold Italic'),local('Asap-BoldItalic'),url(https://themes.googleusercontent.com/static/fonts/asap/v1/_sVKdO-TLWvaH-ptGimJBbO3LdcAZYWl9Si6vvxL-qU.woff) format('woff')}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;src:local('Roboto Regular'),local('Roboto-Regular'),url(https://themes.googleusercontent.com/static/fonts/roboto/v9/CrYjSnGjrRCn0pd9VQsnFOvvDin1pK8aKteLpeZ5c0A.woff) format('woff')}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;src:local('Roboto Bold'),local('Roboto-Bold'),url(https://themes.googleusercontent.com/static/fonts/roboto/v9/d-6IYplOFocCacKzxwXSOLO3LdcAZYWl9Si6vvxL-qU.woff) format('woff')}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;src:local('Roboto Black'),local('Roboto-Black'),url(https://themes.googleusercontent.com/static/fonts/roboto/v9/mnpfi9pxYH-Go5UiibESIrO3LdcAZYWl9Si6vvxL-qU.woff) format('woff')} -------------------------------------------------------------------------------- /_includes/posts/2016/11/29/includes10_downloading_animated_svgs.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | 66 | 70 | 74 | 78 |
79 |
80 |

Figure 10. A downloading progress icon animation that demonstrates a combination of several techniques discussed in this blog post. Android source code is available on GitHub: (a) in-progress download and (b) download complete. Click the icon to start its animation.

81 |
82 | -------------------------------------------------------------------------------- /_includes/posts/2016/11/29/includes1_drawing_paths_path_commands.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 131 |
132 |

133 | Figure 1. Understanding how paths are drawn using path commands. The numbers 134 | in the diagrams match the position of the path after each command is executed. In each diagram, the top left coordinate 135 | is (0,0) and the bottom right coordinate is (12,12). The source code 136 | for each icon is available on 137 | GitHub.

138 |
139 | -------------------------------------------------------------------------------- /_includes/posts/2016/11/29/includes2_transforming_paths_demo.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 155 |
156 |

Figure 2. The effects of different combinations of <group> transformations on a play, pause, and record icon. The order of the checkboxes matches the order in which the transformations are applied in the sample code above. Android source code for each icon is available on GitHub.

157 |
158 | -------------------------------------------------------------------------------- /_includes/posts/2016/11/29/includes3_transforming_paths_animated_svgs.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 57 |
58 | 62 | 66 |
67 |
68 |

Figure 3. Understanding how <group> transformations can be used to create icon animations. Android source code for each is available on GitHub: (a) 69 | expand to collapse, (b) 70 | alarm clock, and (c) 71 | radio button. Click each icon to start its animation.

72 |
73 | -------------------------------------------------------------------------------- /_includes/posts/2016/11/29/includes4_transforming_paths_indeterminate_progress.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 18 | 22 |
23 |
24 |

Figure 4. Understanding how scale and translation are used to animate a horizontal indeterminate progress indicator (source code).

25 |
26 | -------------------------------------------------------------------------------- /_includes/posts/2016/11/29/includes5_trimming_stroked_paths_demo.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
9 |
10 | 11 |
12 |
trimPathStart="0"
13 |
14 |
15 |
16 | 17 |
18 |
trimPathEnd="0.5"
19 |
20 |
21 |
22 | 23 |
24 |
trimPathOffset="0"
25 |
26 |
27 |
28 |

Figure 5. Understanding the effects of the trimPathStart, trimPathEnd, and trimPathOffset properties on a stroked path.

29 |
30 | -------------------------------------------------------------------------------- /_includes/posts/2016/11/29/includes7_trimming_stroked_paths_indeterminate_progress.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 18 | 22 | 26 | 30 | 34 |
35 |
36 |

Figure 7. Understanding how rotation and a stroked trim path are used to animate a circular indeterminate progress indicator (source code).

37 |
-------------------------------------------------------------------------------- /_includes/posts/2016/11/29/includes9_clipping_paths_animated_svgs.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 80 |
81 | 85 | 89 |
90 |
91 |

Figure 9. Understanding how <clip-path>s can be used to create icon animations. Android source code for each is available on GitHub: (a) hourglass, (b) eye visibility, and (c) heart fill/break. Click each icon to start its animation.

92 |
93 | -------------------------------------------------------------------------------- /_includes/social.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:'social-media';src:url('/assets/fonts/social-media.eot?55063482');src:url('/assets/fonts/social-media.eot?55063482#iefix') format('embedded-opentype'),url('/assets/fonts/social-media.woff?55063482') format('woff'),url('/assets/fonts/social-media.ttf?55063482') format('truetype'),url('.assets/fonts/social-media.svg?55063482#social-media') format('svg');font-weight:normal;font-style:normal}[class^="icon-"]:before,[class*=" icon-"]:before{font-family:"social-media";font-style:normal;font-weight:normal;speak:none;display:inline-block;text-decoration:inherit;width:1em;text-align:center;font-variant:normal;text-transform:none}.icon-gplus:before{content:'\e801'}.icon-rss:before{content:'\e800'}.icon-twitter:before{content:'\e802'}.icon-github-circled:before{content:'\e803'} -------------------------------------------------------------------------------- /_includes/style.min.css: -------------------------------------------------------------------------------- 1 | body{background:#fff;font-size:.95em;font-family:Asap,sans-serif!important;line-height:1.5em}#content{width:calc(100% - 360px);margin-right:40px;float:left}#content h1{font-family:Roboto,sans-serif;font-size:2.75em;font-weight:900;line-height:1em;margin:0 0 .5em;color:#222}#content h2{font-family:Roboto,sans-serif;font-weight:700;font-size:1.75em;line-height:1.3em;margin:0 0 1em;color:#222}#content h3{font-family:Roboto,sans-serif;font-weight:700;font-size:1.3em;margin:1em 0 1em;color:#222;margin-top:2em}#content h4{font-family:Roboto,sans-serif;font-weight:400;font-size:1.2em;margin:0 0 1em}#content h5{font-weight:700;margin:2em 0 1em}#content p{margin:1em 0}#content p.lead{font-size:1.5em;line-height:1.6em;margin-top:0}#content blockquote{color:#666;border-left:.5em #690 solid;padding:0 1em;font-style:italic}#content hr{border:0;border-top:1px #222 solid;margin:20px 0}#content hr.footnote-divider{width:60%}#info{float:right;width:320px}#info h3{font-family:Roboto,sans-serif;font-weight:700;font-size:1.3em;margin:1em 0 1em;color:#222}#info h4{font-family:Roboto,sans-serif;font-weight:400;font-size:1.2em;margin:0 0 1em}#info p{margin:1em 0}#info p.lead{font-size:1.5em;line-height:1.6em;margin-top:0}#info .box{padding:20px 0;border-top:1px #eee solid}#info .box:first-child{border:0;margin-top:0;padding-top:0}#info .gbadge{padding:40px 0 0;text-align:center}#blog-header{font-family:Roboto,sans-serif;font-weight:400;font-size:1.3em;margin:0 0 1em}#header{margin:0 auto;padding:10px 0;background:#222}#header ul li{padding-left:30px;float:left}#header nav{font-family:Roboto,sans-serif;text-transform:uppercase;font-size:.8em;margin:50px -13px 0}#header nav a{color:#9c0;text-decoration:none;padding:13px;border:0}#header #blog-header{float:left;text-transform:uppercase;margin-top:50px}#header #blog-header a{margin-left:-9px;margin-top:-9px;color:#fff;text-decoration:none;padding:9px;border-radius:1px;-webkit-transition:background-color .4s;-moz-transition:background-color .4s;-o-transition:background-color .4s;transition:background-color .4s}#header #blog-header a:hover{background:#000;color:#9c0}.post-title a{color:#222;-webkit-transition:color .3s;-moz-transition:color .3s;-o-transition:color .3s;transition:color .3s;font-weight:700}.post-title a:hover{color:#690;text-decoration:underline}a{color:#690;text-decoration:none}a:hover{color:#9c0;text-decoration:underline}a code{color:#690}a:hover code{color:#9c0;text-decoration:underline}.no-border a,a.no-border{text-decoration:none}li{margin:.75em 0}ol,ul{padding-left:2.5em}td,th{padding:.5em;border:1px #ccc solid}thead{background:#f5f5f5;border-bottom:5px #ccc solid}.gist td,.highlighttable td{padding:0;background:0 0;border:0}.container{width:90%;margin:0 auto;max-width:1400px}#social{background:url(/assets/images/android1x.png) no-repeat left bottom;padding-top:.5em;height:32px;background-size:contain;text-align:right;font-size:16px}#social a{text-decoration:none;color:#fff;background:#333;width:32px;height:32px;display:inline-block;border-radius:32px;-webkit-transition:background-color .2s;-moz-transition:background-color .2s;-o-transition:background-color .2s;transition:background-color .2s;padding:2px;text-align:center}#social a:hover{text-decoration:none}#social a.rss{background:#ff8300}#social a.rss:hover{background:#f07000}#social a.twitter{background:#00aced}#social a.twitter:hover{background:#0096cf}#social a.github{background:#4183c4}#social a.github:hover{background:#2d70b3}#social a.googleplus{background:#d14836}#social a.googleplus:hover{background:#c23927}#social a i{line-height:32px}.blog-index{padding:20px 0}.snippet{border-bottom:1px #eee solid;padding-bottom:50px;margin-bottom:40px}.snippet footer{margin-top:1em}.snippet a[class=read-more]{background:#ececec;display:inline-block;padding:.5em 1em .4em 1em;margin-right:.5em;text-decoration:none;color:#6e6e6e;border-radius:1px;border-bottom:0;-webkit-transition:background-color .4s;-moz-transition:background-color .4s;-o-transition:background-color .4s;transition:background-color .4s}.snippet a[class=read-more]:hover{background:#690;text-shadow:none;color:#f8f8f8}.mod-pagination{text-align:center}.mod-pagination .prev{float:left}.mod-pagination .next{float:right}#post-last-updated{font-style:italic;color:#777;margin:1em 0}.post-pagination{text-align:center}.post-pagination .prev{float:right}.post-pagination .next{float:left}.post-pagination:after{content:" ";display:table;clear:both}.post-comments{padding-top:20px}.side{float:left;width:180px;margin:1em 0}.side h4{margin-top:0}.share-buttons{padding-top:1em}.snippet .side{margin-top:0;width:120px}.snippet .date-posted{font-style:normal;text-transform:uppercase}.snippet .body{width:calc(100% - 160px)}.date-block{width:100%;text-align:center;display:inline-block;background:#690;color:#fff;padding:.5em 0;border-radius:1px;margin-top:-2px}.related{padding:0}.related li{border-bottom:1px #ccc solid;list-style-type:none;margin:0}.related li:last-child{border:0}.related li a{background:#f5f5f5;padding:20px;display:block;border:0}.related li a:hover{background:#f5f5f5;padding:20px;display:block;border:0}.related li.featured a{background:#eaeaea}.body{float:right;width:calc(100% - 220px)}#copyright{padding:20px 0;clear:both;text-align:center}#responsive-home-page-first-ad{padding-bottom:40px}#responsive-home-page-bottom-ad{padding-top:20px}#responsive-post-bottom-ad{padding-top:20px}#responsive-post-header-ad{overflow:hidden}#blog-archives h3.year{float:left;width:50px;margin-right:10px;margin-top:8px;line-height:normal}#blog-archives ul{width:calc(100% - 60px);float:left;padding:0;margin:0;margin-bottom:2em}#blog-archives ul:last-child{border:0}#blog-archives li{padding:.5em;list-style-type:none;margin:0}#blog-archives li:last-child{border:0}#blog-archives span{width:60px;text-align:right;padding:0 10px;display:block;float:left;margin-right:10px;color:#666}code{font-size:.95em;color:#c00;font-family:Consolas,Menlo,Monaco,'Lucida Console','Liberation Mono','DejaVu Sans Mono','Courier New',monospace;line-height:1.3em}pre{padding:10px 10px;overflow-x:auto;border-radius:1px;border:1px #ccc solid;font-size:.85em;font-family:Consolas,Menlo,Monaco,'Lucida Console','Liberation Mono','DejaVu Sans Mono','Courier New',monospace;line-height:1.3em;background:#f5f5f5}pre code{font-size:1em;color:#444;background:0 0;padding:0}.linenos{width:10px}.linenos pre{border-top-right-radius:0;border-bottom-right-radius:0}.scrollable{overflow-x:auto;overflow-y:hidden}.highlight{background:#f5f5f5}.highlighttable{width:100%;overflow:auto}.highlighttable .highlight pre{border-left:0}.hidden-big{display:none}.pull-right{float:right}.clearfix{clear:both}.cf:after,.cf:before{content:" ";display:table}.cf:after{clear:both}.container:after,.container:before{content:" ";display:table}.container:after{clear:both}@media (max-width:1250px){.main.container{width:100%}#content{float:none;width:90%;padding:0 5%}#info{clear:both;width:90%;padding:40px 5%;margin-top:40px;background:#f5f5f5}#info .right{width:calc(100% - 340px);margin:0;float:right}#info .gbadge{width:300px;text-align:left;float:left;margin:0;border:0;padding:0}#info #promotion2048{display:none}}@media (max-width:960px){.body,.side,.snippet .body,.snippet .side,header h1{width:100%;float:none}.date-block{width:100px;padding:.5em}.share-buttons div{display:inline}.related li,.related li:last-child{float:left;margin:0 10px 10px 0;border:1px #ccc solid;width:22%}h2.post-title{margin-top:0}.author{display:inline}.hidden-big{display:inherit}.side h4{margin-top:1em}.side.suggested{clear:both;border-top:1px #9c0 dotted;margin-top:40px;padding-top:40px}}@media (max-width:600px){ol,ul{padding-left:1em}li{margin:1em 0}#content h1{font-family:Roboto,sans-serif;font-weight:700;font-size:1.75em;line-height:1.3em;margin:0 0 1em;color:#222}.responsive-figure{float:none!important;margin:0 auto!important}#social{text-align:center;background-image:none!important}#info .right{width:100%;float:none;display:block;margin-top:2em}#info .box{border-top:1px #eaeaea solid}#info .gbadge{width:100%;float:none;padding:40px 0 0;text-align:center}#info #promotion2048{display:block}.related li,.related li:last-child{float:none;border:0;border-bottom:1px #ccc solid;list-style-type:none;margin:0;width:100%}.related li:last-child{float:none;border:0}#header{padding:10px 0}#header #blog-header{margin:10px 0;width:100%;text-align:center}#header #blog-header a{margin:-2px;padding:2px}#header nav{margin:0;clear:both;float:none;text-align:center}#header #blog-header a:hover,#header nav a:hover{background:0 0}#blog-archives h3.year{display:block;float:none;line-height:0}#blog-archives ul{width:100%;float:none}#blog-archives li{padding-left:0;padding-right:0;border-bottom:1px #eee solid}#blog-archives li span{padding:0;padding-left:5px;float:right;text-align:right;margin:0;display:block;color:#666}.side{margin-top:0}img#profile-picture{width:140px!important;height:auto}}img#profile-picture{border-radius:50%;width:200px;height:auto;margin-left:5px;margin-bottom:5px;float:right}@media only screen and (-moz-min-device-pixel-ratio:1.5),only screen and (-o-min-device-pixel-ratio:3/2),only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min-devicepixel-ratio:1.5),only screen and (min-resolution:1.5dppx){#social{background:url(/assets/images/android2x.png) no-repeat left bottom;background-size:contain}}@media print{body{font-size:.75em}#copyright,#header,#info,#responsive-home-page-bottom-ad,#responsive-post-bottom-ad,#responsive-post-header-ad,#social,.share-buttons,.side{display:none}.post-pagination{visibility:hidden;page-break-after:always}a[href]:after{content:none!important}}html{-webkit-text-size-adjust:none}.body{max-height:999999px}.fb-like.fb_iframe_widget span{vertical-align:top!important} -------------------------------------------------------------------------------- /_includes/syntax.css: -------------------------------------------------------------------------------- 1 | .highlight{background:#fff}.highlight .c{color:#888;font-style:italic}.highlight .err{color:#a61717;background-color:#e3d2d2}.highlight .k{font-weight:bold}.highlight .o{font-weight:bold}.highlight .cm{color:#888;font-style:italic}.highlight .cp{color:#999;font-weight:bold}.highlight .c1{color:#888;font-style:italic}.highlight .cs{color:#999;font-weight:bold;font-style:italic}.highlight .gd{color:#000;background-color:#fdd}.highlight .gd .x{color:#000;background-color:#faa}.highlight .ge{font-style:italic}.highlight .gr{color:#a00}.highlight .gh{color:#999}.highlight .gi{color:#000;background-color:#dfd}.highlight .gi .x{color:#000;background-color:#afa}.highlight .go{color:#888}.highlight .gp{color:#555}.highlight .gs{font-weight:bold}.highlight .gu{color:#aaa}.highlight .gt{color:#a00}.highlight .kc{font-weight:bold}.highlight .kd{font-weight:bold}.highlight .kp{font-weight:bold}.highlight .kr{font-weight:bold}.highlight .kt{color:#458;font-weight:bold}.highlight .m{color:#099}.highlight .s{color:#d14}.highlight .na{color:teal}.highlight .nb{color:#0086b3}.highlight .nc{color:#458;font-weight:bold}.highlight .no{color:teal}.highlight .ni{color:purple}.highlight .ne{color:#900;font-weight:bold}.highlight .nf{color:#900;font-weight:bold}.highlight .nn{color:#555}.highlight .nt{color:navy}.highlight .nv{color:teal}.highlight .ow{font-weight:bold}.highlight .w{color:#bbb}.highlight .mf{color:#099}.highlight .mh{color:#099}.highlight .mi{color:#099}.highlight .mo{color:#099}.highlight .sb{color:#d14}.highlight .sc{color:#d14}.highlight .sd{color:#d14}.highlight .s2{color:#d14}.highlight .se{color:#d14}.highlight .sh{color:#d14}.highlight .si{color:#d14}.highlight .sx{color:#d14}.highlight .sr{color:#009926}.highlight .s1{color:#d14}.highlight .ss{color:#990073}.highlight .bp{color:#999}.highlight .vc{color:teal}.highlight .vg{color:teal}.highlight .vi{color:teal}.highlight .il{color:#099}.linenos pre{background:#eee}.linenos pre code{color:#aaa} -------------------------------------------------------------------------------- /_js/build/.gitignore: -------------------------------------------------------------------------------- 1 | *.js -------------------------------------------------------------------------------- /_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 |
6 |

{{ page.title | escape }}

7 |
8 |
9 |
10 | Posted 11 | 12 |
13 | 20 |
21 |
22 | 23 |
24 | 29 |
30 | 33 |
34 | {{ content }} 35 |
36 | {% if page.updated %} 37 |
Last updated {{ page.updated | date: "%B %-d, %Y" }}.
38 | {% endif %} 39 |
40 | {% if page.previous %} 41 | 42 | {% endif %} 43 | {% if page.next %} 44 | 45 | {% endif %} 46 |
47 |
48 | 53 | 57 |
58 | {% if page.disable_comments == null %} 59 |
60 |
61 | 62 |
63 | {% endif %} 64 |
65 | {% if page.related %} 66 |
67 |

Related Posts

68 | 74 |
75 | {% endif %} 76 |
77 | 78 |
79 | -------------------------------------------------------------------------------- /_posts/2012-05-21-correctly-managing-your-sqlite-database.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Correctly Managing your SQLite Database' 4 | date: 2012-05-21 5 | permalink: /2012/05/correctly-managing-your-sqlite-database.html 6 | related: ['/2012/06/content-resolvers-and-content-providers.html', 7 | '/2012/07/understanding-loadermanager.html', 8 | '/2012/10/sqlite-contentprovider-thread-safety.html'] 9 | --- 10 | One thing that I've noticed other Android developers having trouble with is properly 11 | setting up their `SQLiteDatabase`. Often times, I come across questions on StackOverflow 12 | asking about error messages such as, 13 | 14 | ``` 15 | E/Database(234): Leak found 16 | E/Database(234): Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed 17 | ``` 18 | 19 | As you have probably figured out, this exception is thrown when you have opened more 20 | `SQLiteDatabase` instances than you have closed. Managing the database can be complicated 21 | when first starting out with Android development, especially to those who are just beginning 22 | to understand the `Activity` lifecycle. The easiest solution is to make your database 23 | instance a singleton instance across the entire application's lifecycle. This will ensure 24 | that no leaks occur, and will make your life a lot easier since it eliminates the 25 | possibility of forgetting to close your database as you code. 26 | 27 | 28 | 29 | Here are two examples that illustrates three possible approaches in managing your 30 | singleton database. These will ensure safe access to the database throughout the application. 31 | 32 | ### Approach #1: Use a Singleton to Instantiate the `SQLiteOpenHelper` 33 | 34 | Declare your database helper as a static instance variable and use the Singleton 35 | pattern to guarantee the singleton property. The sample code below should give you a good 36 | idea on how to go about designing the `DatabaseHelper` class correctly. 37 | 38 | The static `getInstance()` method ensures that only one `DatabaseHelper` 39 | will ever exist at any given time. If the `sInstance` object has not been initialized, 40 | one will be created. If one has already been created then it will simply be returned. 41 | You should not initialize your helper object using with `new DatabaseHelper(context)`! 42 | Instead, always use `DatabaseHelper.getInstance(context)`, as it guarantees that only one 43 | database helper will exist across the entire application's lifecycle. 44 | 45 | ```java 46 | public class DatabaseHelper extends SQLiteOpenHelper { 47 | 48 | private static DatabaseHelper sInstance; 49 | 50 | private static final String DATABASE_NAME = "database_name"; 51 | private static final String DATABASE_TABLE = "table_name"; 52 | private static final int DATABASE_VERSION = 1; 53 | 54 | public static synchronized DatabaseHelper getInstance(Context context) { 55 | 56 | // Use the application context, which will ensure that you 57 | // don't accidentally leak an Activity's context. 58 | // See this article for more information: http://bit.ly/6LRzfx 59 | if (sInstance == null) { 60 | sInstance = new DatabaseHelper(context.getApplicationContext()); 61 | } 62 | return sInstance; 63 | } 64 | 65 | /** 66 | * Constructor should be private to prevent direct instantiation. 67 | * make call to static method "getInstance()" instead. 68 | */ 69 | private DatabaseHelper(Context context) { 70 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 71 | } 72 | } 73 | ``` 74 | 75 | ### Approach #2: Wrap the `SQLiteDatabase` in a `ContentProvider` 76 | 77 | This is also a nice approach. For one, the new `CursorLoader` class requires 78 | `ContentProvider`s, so if you want an Activity or Fragment to implement `LoaderManager.LoaderCallbacks` 79 | with a `CursorLoader` (as discussed in this post), 80 | you'll need to implement a `ContentProvider` for your application. Further, you don't need to worry 81 | about making a singleton database helper with `ContentProvider`s. Simply call `getContentResolver()` 82 | from the Activity and the system will take care of everything for you (in other words, there is no 83 | need for designing a Singleton pattern to prevent multiple instances from being created). 84 | 85 | Leave a comment if this helped or if you have any questions! 86 | -------------------------------------------------------------------------------- /_posts/2012-05-24-using-newinstance-to-instantiate-a-fragment.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Using newInstance() to Instantiate a Fragment' 4 | date: 2012-05-24 5 | permalink: /2012/05/using-newinstance-to-instantiate.html 6 | --- 7 | I recently came across an interesting question on StackOverflow regarding Fragment instantiation: 8 | 9 | > What is the difference between `new MyFragment()` and `MyFragment.newInstance()`? 10 | > Should I prefer one over the other? 11 | 12 | Good question. The answer, as the title of this blog suggests, is a matter of proper design. In this 13 | case, the `newInstance()` method is a "static factory method," allowing us to initialize and setup a 14 | new `Fragment` without having to call its constructor and additional setter methods. Providing static 15 | factory methods for your fragments is good practice because it encapsulates and abstracts the steps 16 | required to setup the object from the client. For example, consider the following code: 17 | 18 | 19 | 20 | ```java 21 | public class MyFragment extends Fragment { 22 | 23 | /** 24 | * Static factory method that takes an int parameter, 25 | * initializes the fragment's arguments, and returns the 26 | * new fragment to the client. 27 | */ 28 | public static MyFragment newInstance(int index) { 29 | MyFragment f = new MyFragment(); 30 | Bundle args = new Bundle(); 31 | args.putInt("index", index); 32 | f.setArguments(args); 33 | return f; 34 | } 35 | 36 | } 37 | ``` 38 | 39 | Rather than having the client call the default constructor and manually set the fragment's arguments 40 | themselves, we provide a static factory method that does this for them. This is preferred over the 41 | default constructor for two reasons. One, it's convenient for the client, and two, it enforces well-defined 42 | behavior. By providing a static factory method, we protect ourselves from bugs down the line—we no 43 | longer need to worry about accidentally forgetting to initialize the fragment's arguments or incorrectly doing so. 44 | 45 | Overall, while the difference between the two is mostly just a matter of design, this difference is really 46 | important because it provides another level of abstraction and makes code a lot easier to understand. 47 | 48 | Feel free to leave a comment if this blog post helped (it will motivate me to write more in the future)! :) 49 | -------------------------------------------------------------------------------- /_posts/2012-05-26-reaping-benefits-of-the-loadermanager.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Reaping the Benefits of the LoaderManager' 4 | date: 2012-05-26 5 | permalink: /2012/05/why-you-should-use-loadermanager.html 6 | --- 7 | With Android 3.0 came the introduction of the `LoaderManager` class, an abstract 8 | class associated with an `Activity` or `Fragment` for managing one or 9 | more `Loader` instances. The `LoaderManager` class is one of my favorite 10 | additions to the Android framework for a number of reasons, but mostly because it _significantly_ 11 | reduces code complexity and makes your application run a lot smoother. Implementing data loaders 12 | with the `LoaderManager` is simple to implement, and takes care of everything about 13 | lifecycle management so are much less error prone. 14 | 15 | 16 | 17 | While applications are free to write their own loaders for loading various types of data, the 18 | most common (and simplest) use of the `LoaderManager` is with a `CursorLoader`. 19 | When done correctly, the `CursorLoader` class offloads the work of loading data on a thread, 20 | and keeps the data persistent during short term activity refresh events, such as an orientation change. 21 | In addition to performing the initial query, the `CursorLoader` registers a 22 | `ContentObserver` with the dataset you requested and calls `forceLoad()` 23 | on itself when the data set changes, and is thus auto-updating. This is extremely convenient for 24 | the programmer, as he doesn't have to worry about performing these queries himself. Further, 25 | for bigger screens it becomes more important that you query on a separate thread since configuration 26 | changes involve recreating the entire view layout, a complex operation that can cause disasters 27 | when blocked. 28 | 29 | As mentioned earlier, one could implement his or her class to load data on a separate 30 | thread using an `AsyncTask` or even a `Thread`. 31 | The point, however, is that the `LoaderManager` does this all for you, so 32 | it's convenient for the developer, less error prone, and simple to implement. Of course 33 | it is possible to use an `AsyncTask` to keep your application UI thread friendly, 34 | but it will involve a lot more code, and implementing your class so that it will retain the 35 | loaded `Cursor` over the twists and turns of the `Activity` lifecycle 36 | won't be simple. The bottom line is that `LoaderManager` will do this automatically 37 | for you, as well as taking care of correctly creating and closing the `Cursor` 38 | based on the `Activity` lifecycle. 39 | 40 | To use `LoaderManager` with (or without) the `CursorLoader` 41 | in an app targeting pre-Honeycomb devices, you should make use of the classes provided 42 | in the Android Support Package, including the `FragmentActivity` class. A 43 | `FragmentActivity` is just an `Activity` that has been created 44 | for Android compatibility support, and does not require the use of fragments in your 45 | application. When transitioning from an `Activity`s to `FragmentActivity`s, 46 | be extremely careful that you use the `getSupportLoaderManager()` instead of 47 | `getLoaderManager()`. `FragmentActivity` extends `Activity`, 48 | thus inheriting all of its methods, and as a result the compiler will not complain if you 49 | accidentally mix up these methods, so be very careful! 50 | 51 |

Leave a comment if you have any questions or criticisms... or just to let me know 52 | that you managed to read through this entire post without getting distracted! I'm also 53 | open to providing more explicit code samples if anyone asks. -------------------------------------------------------------------------------- /_posts/2012-05-30-basic-android-debugging-with-logs.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Basic Android Debugging with Logs' 4 | date: 2012-05-30 5 | permalink: /2012/05/intro-to-android-debug-logging.html 6 | --- 7 | As with most areas in software engineering, debugging is a crucial aspect 8 | of Android development. Properly setting up your application for debugging 9 | can save you hours of work and frustration. Unfortunately, in my experience 10 | not many beginners learn how to properly make use of the utility classes 11 | provided in the Android SDK. Unless you are an experienced developer, it 12 | is my personal belief that Android debugging should follow a _pattern_. 13 | This will prove beneficial for a couple reasons: 14 | 15 | 16 | 17 | + **It allows you to anticipate bugs down the line.** Setting up your development 18 | work space for debugging will give you a head start on bugs you might encounter 19 | in the future. 20 | 21 | + **It gives you centralized control over the debugging process.** Disorganized and 22 | sparse placement of log messages in your class can clutter your logcat output, making 23 | it difficult to interpret debugging results. The ability to toggle certain groups 24 | of log messages on/off can make your life a whole lot easier, especially if your 25 | application is complex. 26 | 27 | ### The `Log` Class 28 | 29 | For those of you who don't know, the Android SDK includes a useful logging 30 | utility class called `android.util.Log`. The class allows you to 31 | log messages categorized based severity; each type of logging message has 32 | its own message. Here is a listing of the message types, and their respective 33 | method calls, ordered from lowest to highest priority: 34 | 35 | + The `Log.v()` method is used to log verbose messages. 36 | + The `Log.d()` method is used to log debug messages. 37 | + The `Log.i()` method is used to log informational messages. 38 | + The `Log.w()` method is used to log warnings. 39 | + The `Log.e()` method is used to log errors. 40 | + The `Log.wtf()` method is used to log events that should never happen 41 | ("wtf" being an abbreviation for "What a Terrible Failure", of course). 42 | You can think of this method as the equivalent of Java's `assert` method. 43 | 44 | One should _always_ consider a message's type when assigning log messages to 45 | one of the six method calls, as this will allow you to 46 | filter your logcat output 47 | when appropriate. It is also important to understand when it is acceptable to 48 | compile log messages into your application: 49 | 50 | + **Verbose logs should never be compiled into an application except during development.** 51 | When development is complete and you are ready to release your application to the world, 52 | you should remove all verbose method calls either by commenting them out, or using 53 | ProGuard to remove any verbose log statements directly from the bytecode of your 54 | compiled JAR executable, as described in Christopher's answer in this 55 | StackOverflow post. 56 | + **Debug logs** are compiled in but are ignored at runtime. 57 | + **Error**, **warning**, and **informational** logs are always kept. 58 | 59 | ### A Simple Pattern 60 | 61 | A simple way to organize debugging is with the sample pattern implemented 62 | below. A global, static string is given to represent the specific class 63 | (an Activity in this example, but it could be a service, an adapter, 64 | anything), and a boolean variable to represent whether or not log 65 | messages should be printed to the logcat. 66 | 67 | ```java 68 | public class SampleActivity extends Activity { 69 | 70 | /** 71 | * A string constant to use in calls to the "log" methods. Its 72 | * value is often given by the name of the class, as this will 73 | * allow you to easily determine where log methods are coming 74 | * from when you analyze your logcat output. 75 | */ 76 | private static final String TAG = "SampleActivity"; 77 | 78 | /** 79 | * Toggle this boolean constant's value to turn on/off logging 80 | * within the class. 81 | */ 82 | private static final boolean VERBOSE = true; 83 | 84 | @Override 85 | public void onCreate(Bundle savedInstanceState) { 86 | super.onCreate(savedInstanceState); 87 | if (VERBOSE) Log.v(TAG, "+++ ON CREATE +++"); 88 | } 89 | 90 | @Override 91 | public void onStart() { 92 | super.onStart(); 93 | if (VERBOSE) Log.v(TAG, "++ ON START ++"); 94 | } 95 | 96 | @Override 97 | public void onResume() { 98 | super.onResume(); 99 | if (VERBOSE) Log.v(TAG, "+ ON RESUME +"); 100 | } 101 | } 102 | ``` 103 | 104 | Don't be afraid to be creative in how you print your log messages to the logcat! 105 | For instance, when the sample activity above is launched, the resulting logcat 106 | is presented in an nicely formatted and human-readable way: 107 | 108 | ``` 109 | V SampleActivity +++ ON CREATE +++ 110 | V SampleActivity ++ ON START++ 111 | V SampleActivity + ON RESUME + 112 | ``` 113 | 114 | ### Conclusion 115 | 116 | In this post, I have covered the basics in which Android debugging can (and 117 | should) be performed. In a future post, I will go into a bit more depth by 118 | providing some more advanced patterns that will give you more control over 119 | how debugging is performed at runtime. 120 | 121 | Leave a comment if this helped... it'll motivate me to write more of these 122 | blog posts in the future! :) -------------------------------------------------------------------------------- /_posts/2012-06-04-ensuring-compatibility-with-utility-class.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Ensuring Compatibility with a Utility Class' 4 | date: 2012-06-14 5 | permalink: /2012/06/compatability-manager-utility-class.html 6 | --- 7 | This post introduces the concept of a **utility class** and gives a simple 8 | example of how you can use one to tidy up your code. As Android projects grow in size, it 9 | becomes increasingly important that your code remains organized and well-structured. 10 | Providing a utility class for commonly called methods can help tremendously in reducing 11 | the complexity of your project, allowing you to structure your code in a readable and 12 | easily understandable way. 13 | 14 | 15 | 16 | Here's a simple example. Let's say you are building an Android application that frequently 17 | checks the device's SDK version code, to ensure backward compatibility. You'll need to 18 | use the constants provided in the `android.os.Build.VERSION_CODES` class, 19 | but these constants are long and can quickly clutter up your code. In this case, 20 | it might be a good idea to create a `CompatabilityUtil` utility class. 21 | A sample implementation is given below: 22 | 23 | ```java 24 | public class CompatibilityUtil { 25 | 26 | /** Get the current Android API level. */ 27 | public static int getSdkVersion() { 28 | return Build.VERSION.SDK_INT; 29 | } 30 | 31 | /** Determine if the device is running API level 8 or higher. */ 32 | public static boolean isFroyo() { 33 | return getSdkVersion() >= Build.VERSION_CODES.FROYO; 34 | } 35 | 36 | /** Determine if the device is running API level 11 or higher. */ 37 | public static boolean isHoneycomb() { 38 | return getSdkVersion() >= Build.VERSION_CODES.HONEYCOMB; 39 | } 40 | 41 | /** 42 | * Determine if the device is a tablet (i.e. it has a large screen). 43 | * 44 | * @param context The calling context. 45 | */ 46 | public static boolean isTablet(Context context) { 47 | return (context.getResources().getConfiguration().screenLayout 48 | & Configuration.SCREENLAYOUT_SIZE_MASK) 49 | >= Configuration.SCREENLAYOUT_SIZE_LARGE; 50 | } 51 | 52 | /** 53 | * Determine if the device is a HoneyComb tablet. 54 | * 55 | * @param context The calling context. 56 | */ 57 | public static boolean isHoneycombTablet(Context context) { 58 | return isHoneycomb() && isTablet(context); 59 | } 60 | 61 | /** This class can't be instantiated. */ 62 | private CompatibilityUtil() { } 63 | } 64 | ``` 65 | 66 | Developers often create a separate package called `[package name].util` for 67 | frequently used utility classes. So for example, if your package name is `com.example.myapp`, 68 | then a nice place to put your utility classes would be in a separate package called 69 | `com.example.myapp.util`. However, remember that there's no need to _over-organize_ 70 | your project. Creating a separate package might be a good idea for a larger project, 71 | but is completely unnecessary if your project contains only 5-10 classes. I might 72 | write a post about package/class organization in the future. For now, check out the 73 | (very well-designed) Google I/O 2011 74 | app's source code. You will learn a lot! 75 | -------------------------------------------------------------------------------- /_posts/2012-06-13-designing-for-backwards-compatibility.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Designing for Backwards Compatibility' 4 | date: 2012-06-13 5 | permalink: /2012/06/designing-for-backwards-compatibility.html 6 | --- 7 | > Note: please read this short post 8 | > before continuing forward. 9 | 10 | A common issue in Android development is backwards compatibility. How can we add cool 11 | new features from the most recent Android API while still ensuring that it runs 12 | correctly on devices running older versions of Android? This post discusses the 13 | problem by means of a simple example, and proposes a scalable, well-designed solution. 14 | 15 | 16 | 17 | ### The Problem 18 | 19 | Let's say we are writing an application that reads and writes pictures to new albums 20 | (i.e. folders) located on external storage, and that we want our application to support 21 | all devices running Donut (Android 1.6, SDK version 4) and above. Upon consulting the 22 | documentation, 23 | we realize there is a slight problem. With the introduction of Froyo (Android 2.2, 24 | SDK version 8) came a somewhat radical change in how external storage was laid out 25 | and represented on Android devices, as well as several new API methods (see 26 | android.os.Environment) 27 | that allow us access to the public storage directories. To ensure backwards compatibility 28 | all the way back to Donut, we must provide two separate implementations: one for older, 29 | pre-Froyo devices, and another for devices running Froyo and above. 30 | 31 | ### Setting up the Manifest 32 | 33 | Before we dive into the implementation, we will first update our `uses-sdk` tag in the Android 34 | manifest. There are two attributes we must set, 35 | 36 | + `android:minSdkVersion="4"`. This attribute defines a minimum API level required for 37 | the application to run. We want our application to run on devices running Donut and above, 38 | so we set its value to `"4"`. 39 | 40 | + `android:targetSdkVersion="15"`. This attribute is a little trickier to understand 41 | (and is incorrectly defined on blogs all over the internet). This attribute specifies 42 | the API level on which the application is designed to run. Preferably we would want 43 | its value to correspond to the most recently released SDK (`"15"`, at the time of this 44 | posting). Strictly speaking, however, its value should be given by the largest SDK 45 | version number that we have tested your application against (we will assume we have 46 | done so for the remainder of this example). 47 | 48 | The resulting tag in our manifest is as follows: 49 | 50 | ``` 51 | 54 | 55 | ``` 56 | 57 | ### Implementation 58 | 59 | Our implementation will consist of an abstract class and two subclasses that extend 60 | it. The abstract `AlbumStorageDirFactory` class enforces a simple contract by 61 | requiring its subclasses to implement the `getAlbumStorageDir` method. The actual 62 | implementation of this method depends on the device's SDK version number. Specifically, 63 | if we are using a device running Froyo or above, its implementation will make use of 64 | new methods introduced in API level 8. Otherwise, the correct directory must be 65 | determined using pre-Froyo method calls, to ensure that our app remains backwards compatible. 66 | 67 | ```java 68 | public abstract class AlbumStorageDirFactory { 69 | 70 | /** 71 | * Returns a File object that points to the folder that will store 72 | * the album's pictures. 73 | */ 74 | public abstract File getAlbumStorageDir(String albumName); 75 | 76 | /** 77 | * A static factory method that returns a new AlbumStorageDirFactory 78 | * instance based on the current device's SDK version. 79 | */ 80 | public static AlbumStorageDirFactory newInstance() { 81 | // Note: the CompatibilityUtil class is implemented 82 | // and discussed in a previous post, entitled 83 | // "Ensuring Compatibility with a Utility Class". 84 | if (CompatabilityUtil.isFroyo()) { 85 | return new FroyoAlbumDirFactory(); 86 | } else { 87 | return new BaseAlbumDirFactory(); 88 | } 89 | } 90 | } 91 | ``` 92 | 93 | The two subclasses and their implementation are given below.The class also provides 94 | a static factory `newInstance` method (note that this method makes use of the 95 | `CompatabilityUtil` utility class, which was both implemented and discussed in a 96 | previous post). 97 | We discuss this method in detail in the next section. 98 | 99 | The `BaseAlbumDirFactory` subclass handles pre-Froyo SDK versions: 100 | 101 | ```java 102 | public class BaseAlbumDirFactory extends AlbumStorageDirFactory { 103 | 104 | /** 105 | * For pre-Froyo devices, we must provide the name of the photo directory 106 | * ourselves. We choose "/dcim/" as it is the widely considered to be the 107 | * standard storage location for digital camera files. 108 | */ 109 | private static final String CAMERA_DIR = "/dcim/"; 110 | 111 | @Override 112 | public File getAlbumStorageDir(String albumName) { 113 | return new File(Environment.getExternalStorageDirectory() 114 | + CAMERA_DIR + albumName); 115 | } 116 | } 117 | ``` 118 | 119 | The `FroyoAlbumDirFactory` subclass handles Froyo and above: 120 | 121 | ```java 122 | public class FroyoAlbumDirFactory extends AlbumStorageDirFactory { 123 | 124 | @Override 125 | public File getAlbumStorageDir(String albumName) { 126 | return new File(Environment.getExternalStoragePublicDirectory( 127 | Environment.DIRECTORY_PICTURES), albumName); 128 | } 129 | } 130 | ``` 131 | 132 | ### Making Sense of the Pattern 133 | 134 | Take a second to study the structure of the code above. Our implementation ensures 135 | compatibility with pre-Froyo devices through a simple design. To ensure compatibility, 136 | we simply request a new `AlbumStorageDirFactory` and call the abstract `getAlbumStorageDir` 137 | method. The subclass is determined and instantiated at runtime depending on the Android 138 | device's SDK version number. See the sample activity below for an example on how an ordinary 139 | Activity might use this pattern to retrieve an album's directory. 140 | 141 | ```java 142 | public class SampleActivity extends Activity { 143 | 144 | private AlbumStorageDirFactory mAlbumFactory; 145 | 146 | @Override 147 | public void onCreate(Bundle savedInstanceState) { 148 | super.onCreate(savedInstanceState); 149 | 150 | // Instantiate the AlbumStorageDirFactory. Instead of 151 | // invoking the subclass' default constructors directly, 152 | // we make use of the Abstract Factory design pattern, 153 | // which encapsulates the inner details. As a result, the 154 | // Activity does not need to know `anything` about the 155 | // compatibility-specific implementation--all of this is 156 | // done behind the scenes within the "mAlbumFactory" object. 157 | mAlbumFactory = AlbumStorageDirFactory.newInstance(); 158 | 159 | // get the album's directory 160 | File sampleAlbumDir = getAlbumDir("sample_album"); 161 | } 162 | 163 | /** 164 | * A simple helper method that returns a File corresponding 165 | * to the album named "albumName". The helper method invokes 166 | * the abstract "getAlbumStorageDir" method, which will return 167 | * correct location of the directory depending on the subclass 168 | * that was returned in "newInstance" (which depends entirely 169 | * on the device's SDK version number). 170 | */ 171 | private File getAlbumDir(String albumName) { 172 | return mAlbumFactory.getAlbumStorageDir(albumName); 173 | } 174 | } 175 | ``` 176 | 177 | There are a couple benefits to organizing the code the way we have: 178 | 179 | + **It's easily extendable.** While there is certainly no need to separate our 180 | implementations into classes for simple examples (such as the one discussed above), 181 | doing so is important when working with large, complicated projects, as it will ensure 182 | changes can quickly be made down the line. 183 | + **It encapsulates the implementation-specific details.** Abstracting these details 184 | from the client makes our code less cluttered and easier to read (note: in this case, 185 | "the client" was the person who wrote the Activity class). 186 | 187 | ### Conclusion 188 | 189 | Android developers constantly write code to ensure backwards compatibility. As projects 190 | expand and applications become more complex, it becomes increasingly important to ensure 191 | your implementation is properly designed. Hopefully this post helped and will encourage 192 | you to more elegant solutions in the future! 193 | 194 | Leave a comment if you have any questions or criticisms... or just to let me know that 195 | you managed to read through this entire post! 196 | -------------------------------------------------------------------------------- /_posts/2012-06-18-why-ice-cream-sandwich-crashes-your-app.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Why Ice Cream Sandwich Crashes your App' 4 | date: 2012-06-18 5 | permalink: /2012/06/app-force-close-honeycomb-ics.html 6 | related: ['/2013/08/fragment-transaction-commit-state-loss.html', 7 | '/2012/06/compatability-manager-utility-class.html', 8 | '/2013/01/inner-class-handler-memory-leak.html'] 9 | --- 10 | The following question has plagued StackOverflow ever since Ice Cream 11 | Sandwich's initial release: 12 | 13 | > My application works fine on devices running Android 2.x, but 14 | > force closes on devices running Honeycomb (3.x) and Ice Cream 15 | > Sandwich (4.x). Why does this occur? 16 | 17 | This is a great question; after all, newer versions of Android are 18 | released with the expectation that old apps will remain compatible 19 | with new devices. In my experience, there are a couple reasons why 20 | this might occur. Most of the time, however, the reason is simple: 21 | _you are performing a potentially expensive operation on the UI 22 | thread_. 23 | 24 | 25 | 26 | ### What is the UI Thread? 27 | 28 | The concept and importance of the application's **main UI thread** 29 | is something every Android developer should understand. Each time an 30 | application is launched, the system creates a thread called "main" 31 | for the application. The main thread (also known as the "UI thread") 32 | is in charge of dispatching events to the appropriate views/widgets 33 | and thus is very important. It's also the thread where your application 34 | interacts with running components of your application's UI. For instance, 35 | if you touch a button on the screen, the UI thread dispatches the touch 36 | event to the view, which then sets its pressed state and posts an invalidate 37 | request to the event queue. The UI thread dequeues this request and then 38 | tells the view to redraw itself. 39 | 40 | This single-thread model can yield poor performance unless Android 41 | applications are implemented properly. Specifically, if the UI thread 42 | was in charge of running everything in your entire application, 43 | performing long operations such as network access or database queries 44 | on the UI thread would block the entire user interface. No event would 45 | be able to be dispatched—including drawing and touchscreen 46 | events—while the long operation is underway. From the user's 47 | perspective, the application will appear to be frozen. 48 | 49 | In these situations, instant feedback is vital. Studies show that 50 | **0.1 seconds** is about the limit for having the user feel that 51 | the system is reacting instantaneously. Anything slower than this 52 | limit will probably be considered as **lag** 53 | (Miller 1968; Card et al. 1991). 54 | While a fraction of a second might not seem harmful, even a tenth 55 | of a second can be the difference between a good review and a bad 56 | review on Google Play. Even worse, if the UI thread is blocked 57 | for more than about five seconds, the user is presented with the 58 | notorious "application not responding" (ANR) dialog and the app is 59 | force closed. 60 | 61 | ### Why Android Crashes Your App 62 | 63 | The reason why your application crashes on Android versions 3.0 and above, 64 | but works fine on Android 2.x is because **Honeycomb and Ice Cream Sandwich 65 | are much stricter about abuse against the UI Thread**. For example, when 66 | an Android device running HoneyComb or above detects a network access on 67 | the UI thread, a `NetworkOnMainThreadException` will be thrown: 68 | 69 | ``` 70 | E/AndroidRuntime(673): java.lang.RuntimeException: Unable to start activity 71 | ComponentInfo{com.example/com.example.ExampleActivity}: android.os.NetworkOnMainThreadException 72 | ``` 73 | 74 | The explanation as to why this occurs is well documented on the Android 75 | developer's site: 76 | 77 | > A `NetworkOnMainThreadException` is thrown when an application 78 | > attempts to perform a networking operation on its main thread. This is 79 | > only thrown for applications targeting the Honeycomb SDK or higher. 80 | > Applications targeting earlier SDK versions are allowed to do networking 81 | > on their main event loop threads, but it's heavily discouraged. 82 | 83 | Some examples of other operations that ICS and Honeycomb _won't_ allow 84 | you to perform on the UI thread are: 85 | 86 | + Opening a `Socket` connection (i.e. `new Socket()`). 87 | + HTTP requests (i.e. `HTTPClient` and `HTTPUrlConnection`). 88 | + Attempting to connect to a remote MySQL database. 89 | + Downloading a file (i.e. `Downloader.downloadFile()`). 90 | 91 | If you are attempting to perform any of these operations on the UI thread, you 92 | _must_ wrap them in a worker thread. The easiest way to do this is to use 93 | of an `AsyncTask`, which allows you to perform asynchronous work on 94 | your user interface. An `AsyncTask` will perform the blocking operations 95 | in a worker thread and will publish the results on the UI thread, without 96 | requiring you to handle threads and/or handlers yourself. 97 | 98 | ### Conclusion 99 | 100 | The reason why I decided to write about this topic is because I have seen it 101 | come up on StackOverflow and other forums countless times. The majority of 102 | the time the error stems from placing expensive operations directly on the UI 103 | thread. To ensure you don't disrupt the user experience, it is very important 104 | to execute Socket connections, HTTP requests, file downloads, and other 105 | long-term operations on a separate Thread. The easiest way to do this is 106 | to wrap your operation in an AsyncTask, which launches a new thread and 107 | allows you to perform asynchronous work on your user interface. 108 | 109 | As always, let me know if this was helpful by +1-ing the post or leaving a 110 | comment below! Feel free to ask questions too... I respond to them quickly. :) 111 | 112 | ### Helpful Links 113 | 114 | Here are some helpful links that might help you get started with `AsyncTask`s: 115 | 116 | + `AsyncTask` documentation 117 | + Multithreading For Performance 118 | -------------------------------------------------------------------------------- /_posts/2012-06-25-content-providers-and-content-resolvers.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Content Providers & Content Resolvers' 4 | date: 2012-06-25 5 | permalink: /2012/06/content-resolvers-and-content-providers.html 6 | --- 7 | Content Providers and Content Resolvers are a common source of confusion for beginning 8 | Android developers. Further, online tutorials and sample code are not sufficient in 9 | describing how the two classes work together to provide access to the Android data 10 | model. This post hopes to fill in this gap by explaining their place in the 11 | `android.content` package. It concludes with a walk through the life of a 12 | simple query to the Content Resolver. 13 | 14 | 15 | 16 | ### The `android.content` Package 17 | 18 | The `android.content` 19 | package contains classes for accessing and publishing data. The Android framework 20 | enforces a **robust** and **secure** data sharing model. Applications are _not_ 21 | allowed direct access to other application's internal data. Two classes in the 22 | package help enforce this requirement: the `ContentResolver` and the `ContentProvider`. 23 | 24 | ### What is the Content Resolver? 25 | 26 | The Content Resolver is the single, global instance in your application that provides 27 | access to your (and other applications') content providers. The Content Resolver 28 | behaves exactly as its name implies: it accepts requests from clients, and _resolves_ 29 | these requests by directing them to the content provider with a distinct authority. 30 | To do this, the Content Resolver stores a mapping from authorities to Content Providers. 31 | This design is important, as it allows a simple and secure means of accessing other 32 | applications' Content Providers. 33 | 34 | The Content Resolver includes the CRUD (create, read, update, delete) methods corresponding 35 | to the abstract methods (insert, query, update, delete) in the Content Provider class. 36 | The Content Resolver does not know the implementation of the Content Providers it is 37 | interacting with (nor does it need to know); each method is passed an URI that specifies 38 | the Content Provider to interact with. 39 | 40 | ### What is a Content Provider? 41 | 42 | Whereas the Content Resolver provides an abstraction from the application's Content Providers, 43 | Content Providers provide an abstraction from the underlying data source 44 | (i.e. a SQLite database). They provide mechanisms for defining data security (i.e. by 45 | enforcing read/write permissions) and offer a standard interface that connects data 46 | in one process with code running in another process. 47 | 48 | Content Providers provide an interface for publishing and consuming data, based around a 49 | simple URI addressing model using the `content://` schema. They enable you to decouple 50 | your application layers from the underlying data layers, making your application 51 | data-source agnostic by abstracting the underlying data source. 52 | 53 | ### The Life of a Query 54 | 55 | So what exactly is the step-by-step process behind a simple query? As described above, 56 | when you query data from your database via the content provider, you don't communicate 57 | with the provider directly. Instead, you use the Content Resolver object to communicate 58 | with the provider. The specific sequence of events that occurs when a query is made 59 | is given below: 60 | 61 | 1. A call to `getContentResolver().query(Uri, String, String, String, String)` is made. 62 | The call invokes the Content Resolver's `query` method, _not_ the `ContentProvider`'s. 63 | 2. When the `query` method is invoked, the Content Resolver parses the `uri` argument 64 | and extracts its authority. 65 | 3. The Content Resolver directs the request to the content provider registered with the 66 | (unique) authority. This is done by calling the Content Provider's `query` method. 67 | 4. When the Content Provider's `query` method is invoked, the query is performed and 68 | a Cursor is returned (or an exception is thrown). The resulting behavior depends 69 | entirely on the Content Provider's implementation. 70 | 71 | ### Conclusion 72 | 73 | An integral part of the 74 | `android.content` 75 | package, the `ContentResolver` and `ContentProvider` classes work together to 76 | ensure secure access to other applications' data. Understanding how the underlying 77 | system works becomes second nature once you've written enough Android code, but I 78 | hope that someone finds this explanation helpful some day. 79 | 80 | Let me know if you have any questions about the process! 81 | -------------------------------------------------------------------------------- /_posts/2012-08-07-exit-application-dialogs-are-evil.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "'Exit Application?' Dialogs Are Evil, Don't Use Them!" 4 | date: 2012-08-07 5 | permalink: /2012/08/exit-application-dialogs-are-evil-dont.html 6 | --- 7 | Here's a question that is worth thinking about: 8 | 9 | > Should I implement an "Exit application?" dialog in my app? 10 | 11 | In my experience, the answer is almost always **no**. Consider the official Flickr app, 12 | as an example. At the main screen, the user clicks the back button and is immediately 13 | prompted with a dialog, questioning whether or not the user wishes to exit the application: 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
21 | Back button pressed. 22 | 24 | An exit dialog is shown. 25 |
(a) Back button pressed.(b) "Exit Flickr?"
33 | 34 | So what went wrong? Well, pretty much everything, at least in my opinion. 35 | Here are the three major flaws I see in Flickr's decision to include the dialog: 36 | 37 | 1. **It slows down the user experience.** An additional click is required to leave the application. 38 | Sure, it doesn't seem like much... but zero clicks is always better than one. Including the 39 | dialog will annoy the occasional meticulous power user and will make it much more likely 40 | that people like me will write-up angry rants about it online. To make matters worse, Flickr's 41 | dialog incorrectly positions the "OK" and "Cancel" buttons, which as of Android 4.0, should be 42 | positioned on the right and left respectively. This is also not a _huge_ deal, but it forces 43 | users to think more than they should need to, and the simple action of exiting the application is 44 | no longer seamless as a result. 45 | 46 | 2. **It is inconsistent.** Name one native Android application that warns the user when they are 47 | about to exit the application. If you can't, that's because there are none. Of all the familiar, 48 | Google-made Android apps (Gmail, Google Drive, etc.), exactly _none_ of them exhibit this 49 | behavior. The user expects the back button to bring him or her back to the top activity on the 50 | Activity Stack; there is no reason why it shouldn't do otherwise in this simple situation. 51 | 52 | 3. **It serves absolutely no purpose.** What baffles me the most, however, is that there is no 53 | reason to confirm exit in the first place. _Maybe_ the dialog would be OK if there was a 54 | long-running operation running in the background that is specific to the Activity (i.e. an 55 | `AsyncTask` that the user might not want canceled). A dialog _might_ also 56 | make sense if the application took a long time to load, for example, a fancy, video intensive FPS like 57 | Dead Trigger. 58 | In Flickr's case, there is no acceptable reason why the user shouldn't be allowed to "back-out" of 59 | the application immediately. 60 | 61 | In my opinion, dialogs are both slow and annoying, and should be used as little as possible. 62 | Always prefer the faster "edit in place" user model (as described 63 | here) 64 | when it comes to saving persistent state, and never prompt the user when they wish to "back-out" of the 65 | application unless you have a _very_ good reason for doing so. 66 | 67 | As always, let me know if you agree or disagree in the comments below! 68 | 69 | **EDIT:** For more discussion on this topic, I recommend reading through the content/comments 70 | of this Google+ post (made by 71 | +Cyril Mottier, 72 | a very talented Android developer recognized by Google as a 73 | Android Developer Expert). -------------------------------------------------------------------------------- /_posts/2012-08-26-follow-this-blog-on-google-currents.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Follow This Blog On Google Currents!' 4 | date: 2012-08-26 5 | permalink: /2012/08/follow-this-blog-on-google-currents_8022.html 6 | --- 7 | 8 | Hi all, 9 | 10 | I've recently made this blog available on Google Currents! Install the application and 11 | subscribe by clicking [this link](https://www.google.com/producer/editions/CAow5Ir3AQ/android_design_patterns). 12 | 13 | If you have never used Google Currents, I strongly recommend that you try it out. It's a 14 | really great way to keep up with the latest news, blogs, and your favorite Google+ streams, 15 | and it works seamlessly offline (which I've found is great for long plane rides). If you're 16 | a long time Flipboard user, I recommend you give it a try as well... in my opinion, Currents 17 | is easier to navigate and feels much more like a native Android application. That said, 18 | I do tend to be a bit biased towards the native Google apps. :P 19 | 20 | 21 | 22 | As always, don't hesitate to leave a comment if you find a bug or have any suggestions on 23 | how I can improve the edition! I'm going to try really hard to keep it up-to-date for those 24 | of you who follow this blog and can't get enough of Google Currents! 25 | 26 | Cheers,
27 | Alex -------------------------------------------------------------------------------- /_posts/2012-09-16-loaders-part4.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Tutorial: AppListLoader (part 4)' 4 | date: 2012-09-16 5 | permalink: /2012/09/tutorial-loader-loadermanager.html 6 | related: ['/2012/07/loaders-and-loadermanager-background.html', 7 | '/2012/07/understanding-loadermanager.html', 8 | '/2012/08/implementing-loaders.html'] 9 | --- 10 | This will be my fourth and final post on Loaders and the LoaderManager. Let me know in the comments if they have been helpful! 11 | Links to my previous Loader-related posts are given below: 12 | 13 | + **Part 1:** Life Before Loaders 14 | + **Part 2:** Understanding the LoaderManager 15 | + **Part 3:** Implementing Loaders 16 | + **Part 4:** Tutorial: AppListLoader 17 | 18 | Due to public demand, I've written a sample application that illustrates how to correctly implement a custom Loader. 19 | The application is named AppListLoader, 20 | and it is a simple demo application that queries and lists all installed applications on your Android device. 21 | The application is a modified, re-thought (and bug-free) extension of the 22 | LoaderCustom.java 23 | sample that is provided in the API Demos. The application uses an `AppListLoader` 24 | (a subclass of `AsyncTaskLoader`) to query its data, and the LoaderManager to 25 | manage the Loader across the Activity/Fragment lifecycle: 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | The AppListLoader registers two `BroadcastReceiver`s which observe/listen for system-wide broadcasts that 34 | impact the underlying data source. The `InstalledAppsObserver` listens for newly installed, updated, or 35 | removed applications, and the `SystemLocaleObserver` listens for locale changes. For example, if the user 36 | changes the language from English to Spanish, the `SystemLocaleObserver` will notify the AppListLoader to 37 | re-query its data so that the application can display each application's name in Spanish (assuming an alternate 38 | Spanish name has been provided). Click "Change language" in the options menu and watch the Loader's seamless 39 | reaction to the event (it's awesome, isn't it? :P). 40 | 41 | Log messages are written to the logcat whenever an important Loader/LoaderManager-related event occurs, so be 42 | sure to run the application while analyzing the logcat! Hopefully it'll give you a better understanding of how 43 | Loaders work in conjunction with the LoaderManager and the Activity/Fragment lifecycle. Be sure to filter the 44 | logcat by application name ("com.adp.loadercustom") for the best results! 45 | 46 | 47 | 48 | 49 | 50 | You can download the application from Google Play by clicking the badge below: 51 | 52 | 53 | 54 | 55 | 56 | The source code is available on GitHub. 57 | An excessive amount of comments flesh out the entire application-Loader workflow. Download it, 58 | import it as an eclipse project, and modify it all you want! 59 | 60 | Let me know if these posts have been helpful by leaving a comment below! As always, 61 | don't hesitate to ask questions either! 62 | -------------------------------------------------------------------------------- /_posts/2012-10-11-sqlite-content-providers-thread-safety.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'SQLite, Content Providers, & Thread Safety' 4 | date: 2012-10-11 5 | permalink: /2012/10/sqlite-contentprovider-thread-safety.html 6 | related: ['/2012/05/correctly-managing-your-sqlite-database.html', 7 | '/2012/06/content-resolvers-and-content-providers.html', 8 | '/2012/07/understanding-loadermanager.html'] 9 | --- 10 | A common source of confusion when implementing `ContentProvider`s is that of thread-safety. 11 | We all know that any potentially expensive query should be asynchronous so as not to block 12 | the UI thread, but when, if ever, is it OK to make calls to the `ContentProvider` from 13 | multiple threads? 14 | 15 | 16 | 17 | ### Threads and Content Providers 18 | 19 | The documentation 20 | on ContentProviders warns that its methods may be called from multiple threads and therefore 21 | must be thread-safe: 22 | 23 | > Data access methods (such as `insert(Uri, ContentValues)` and 24 | > `update(Uri, ContentValues, String, String[]))` may be called from many 25 | > threads at once, and must be thread-safe. 26 | 27 | In other words, Android **does not** synchronize access to the ContentProvider for you. 28 | If two calls to the same method are made simultaneously from separate threads, neither 29 | call will wait for the other. Requiring the client to deal with concurrency themselves 30 | makes sense from a framework developer's point of view. The abstract `ContentProvider` class 31 | cannot assume that its subclasses will require synchronization, as doing so would be 32 | horribly inefficient. 33 | 34 | ### Ensuring Thread Safety 35 | 36 | So now that we know that the ContentProvider is not thread safe, what do we need to 37 | do in order to eliminate potential race conditions? Just make every method 38 | `synchronized`, right? 39 | 40 | Well... no, not necessarily. Consider a ContentProvider that uses a `SQLiteDatabase` 41 | as its backing data source. As per the 42 | documentation, 43 | access to the `SQLiteDatabase` is synchronized by default, thus guaranteeing that 44 | no two threads will ever touch it at the same time. In this case, synchronizing 45 | each of the ContentProvider's methods is both unnecessary and costly. Remember 46 | that a `ContentProvider` serves as a wrapper around the underlying data source; 47 | whether or not you must take extra measures to ensure thread safety often depends 48 | on the data source itself. 49 | 50 | ### Conclusion 51 | 52 | Although the ContentProvider lacks in thread-safety, often times you will find that 53 | no further action is required on your part with respect to preventing potential 54 | race conditions. The canonical example is when your ContentProvider is backed by 55 | a `SQLiteDatabase`; when two threads attempt to write to the database at the same 56 | time, the `SQLiteDatabase` will lock itself down, ensuring that one will wait until 57 | the other has completed. Each thread will be given mutually exclusive access to the 58 | data source, ensuring the thread safety is met. 59 | 60 | This has been a rather short post, so don't hesitate to leave a comment if you have 61 | any clarifying questions. Don't forget to +1 this post below if you found it helpful! -------------------------------------------------------------------------------- /_posts/2013-01-12-use-go-to-implement-android-backends.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'Use Go to Implement your Android Backends' 4 | date: 2013-01-12 5 | permalink: /2013/01/gcm-appengine-golang-android-backends.html 6 | --- 7 | A couple weeks ago I wrote a library 8 | that simplifies the interaction between Go-based application servers and Google Cloud 9 | Messaging servers. I plan on covering GCM (both the application-side and server-side 10 | aspects) in more detail in a future blog post, but for now I will just leave a link 11 | to the library to encourage more people to write their GCM application servers using 12 | the Go Programming Language 13 | (Google App Engine, 14 | hint hint). 15 | 16 | _...but why Go?_ 17 | 18 | I'm glad you asked. There are several reasons: 19 | 20 | 21 | 22 | + Go is modern. Programming languages like C, C++, and Java 23 | are old, designed before the advent of multicore machines, networking, and web 24 | application development. Go was designed to be suitable for writing large Google 25 | programs such as web servers. 26 | 27 | + Go is concise, yet familiar. Tasks that require 40+ lines of code 28 | in Java (i.e. setting up HTTP servers and parsing JSON responses) can be done in 1 or 2 29 | lines. Go significantly reduces the amount of work required to write simple programs, 30 | and yet the language's syntax is not too radical, still resembling the most common 31 | procedural languages. 32 | 33 | + Go is easy to learn. Learn the language in a day: 34 | A Tour of Go and 35 | Effective Go. 36 | 37 | + Go was invented at Google. Enough said. :) 38 | 39 | That's all for now... but expect a lot more on GCM, Google App Engine, and Golang 40 | later! The comments are open as always, and don't forget to +1 this post! 41 | 42 | ### Links 43 | 44 | + Google Cloud Messaging for Go 45 | + Google App Engine 46 | + A Tour of Go 47 | + Effective Go 48 | + golang.org 49 | -------------------------------------------------------------------------------- /_posts/2013-01-14-how-to-leak-a-context-handlers-inner-classes.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 'How to Leak a Context: Handlers & Inner Classes' 4 | date: 2013-01-14 5 | permalink: /2013/01/inner-class-handler-memory-leak.html 6 | updated: '2014-12-12' 7 | related: ['/2012/07/understanding-loadermanager.html', 8 | '/2013/04/activitys-threads-memory-leaks.html', 9 | '/2013/04/retaining-objects-across-config-changes.html'] 10 | --- 11 | Consider the following code: 12 | 13 | ```java 14 | public class SampleActivity extends Activity { 15 | 16 | private final Handler mLeakyHandler = new Handler() { 17 | @Override 18 | public void handleMessage(Message msg) { 19 | // ... 20 | } 21 | }; 22 | } 23 | ``` 24 | 25 | While not readily obvious, this code can cause cause a massive memory leak. 26 | Android Lint will give the following warning: 27 | 28 | > In Android, Handler classes should be static or leaks might occur. 29 | 30 | But where exactly is the leak and how might it happen? Let's determine the 31 | source of the problem by first documenting what we know: 32 | 33 | 34 | 35 | 1. When an Android application first starts, the framework creates a 36 | `Looper` 37 | object for the application's main thread. A `Looper` implements a simple message queue, 38 | processing `Message` 39 | objects in a loop one after another. All major application framework events (such 40 | as Activity lifecycle method calls, button clicks, etc.) are contained inside 41 | `Message` objects, which are added to the `Looper`'s message queue and are processed 42 | one-by-one. The main thread's `Looper` exists throughout the application's lifecycle. 43 | 44 | 2. When a `Handler` 45 | is instantiated on the main thread, it is associated with the `Looper`'s message queue. 46 | Messages posted to the message queue will hold a reference to the `Handler` so that the 47 | framework can call 48 | `Handler#handleMessage(Message)` 49 | when the `Looper` eventually processes the message. 50 | 51 | 3. In Java, non-static inner and anonymous classes hold an implicit reference to their 52 | outer class. Static inner classes, on the other hand, do not. 53 | 54 | So where exactly is the memory leak? It's very subtle, but consider the following code as an example: 55 | 56 | ```java 57 | public class SampleActivity extends Activity { 58 | 59 | private final Handler mLeakyHandler = new Handler() { 60 | @Override 61 | public void handleMessage(Message msg) { 62 | // ... 63 | } 64 | }; 65 | 66 | @Override 67 | protected void onCreate(Bundle savedInstanceState) { 68 | super.onCreate(savedInstanceState); 69 | 70 | // Post a message and delay its execution for 10 minutes. 71 | mLeakyHandler.postDelayed(new Runnable() { 72 | @Override 73 | public void run() { /* ... */ } 74 | }, 1000 * 60 * 10); 75 | 76 | // Go back to the previous Activity. 77 | finish(); 78 | } 79 | } 80 | ``` 81 | 82 | When the activity is finished, the delayed message will continue to live in the main thread's 83 | message queue for 10 minutes before it is processed. The message holds a reference to the 84 | activity's `Handler`, and the `Handler` holds an implicit reference to its outer class (the 85 | `SampleActivity`, in this case). This reference will persist until the message is processed, 86 | thus preventing the activity context from being garbage collected and leaking all of the 87 | application's resources. Note that the same is true with the anonymous Runnable class on 88 | line 15. Non-static instances of anonymous classes hold an implicit reference to their outer 89 | class, so the context will be leaked. 90 | 91 | To fix the problem, subclass the `Handler` in a new file or use a static inner class instead. 92 | Static inner classes do not hold an implicit reference to their outer class, so the activity 93 | will not be leaked. If you need to invoke the outer activity's methods from within the 94 | `Handler`, have the Handler hold a `WeakReference` to the activity so you don't accidentally 95 | leak a context. To fix the memory leak that occurs when we instantiate the anonymous Runnable 96 | class, we make the variable a static field of the class (since static instances of anonymous 97 | classes do not hold an implicit reference to their outer class): 98 | 99 | ```java 100 | public class SampleActivity extends Activity { 101 | 102 | /** 103 | * Instances of static inner classes do not hold an implicit 104 | * reference to their outer class. 105 | */ 106 | private static class MyHandler extends Handler { 107 | private final WeakReference mActivity; 108 | 109 | public MyHandler(SampleActivity activity) { 110 | mActivity = new WeakReference(activity); 111 | } 112 | 113 | @Override 114 | public void handleMessage(Message msg) { 115 | SampleActivity activity = mActivity.get(); 116 | if (activity != null) { 117 | // ... 118 | } 119 | } 120 | } 121 | 122 | private final MyHandler mHandler = new MyHandler(this); 123 | 124 | /** 125 | * Instances of anonymous classes do not hold an implicit 126 | * reference to their outer class when they are "static". 127 | */ 128 | private static final Runnable sRunnable = new Runnable() { 129 | @Override 130 | public void run() { /* ... */ } 131 | }; 132 | 133 | @Override 134 | protected void onCreate(Bundle savedInstanceState) { 135 | super.onCreate(savedInstanceState); 136 | 137 | // Post a message and delay its execution for 10 minutes. 138 | mHandler.postDelayed(sRunnable, 1000 * 60 * 10); 139 | 140 | // Go back to the previous Activity. 141 | finish(); 142 | } 143 | } 144 | ``` 145 | 146 | 147 | The difference between static and non-static inner classes is subtle, but is something 148 | every Android developer should understand. What's the bottom line? **Avoid using non-static 149 | inner classes in an activity if instances of the inner class could outlive the activity's 150 | lifecycle.** Instead, prefer static inner classes and hold a weak reference to the activity inside. 151 | 152 | As always, leave a comment if you have any questions and don't forget to +1 this blog in 153 | the top right corner! :) 154 | -------------------------------------------------------------------------------- /_posts/2014-01-08-redesigning-android-design-patterns.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Redesigning Android Design Patterns 4 | thumbnails: ['/assets/images/posts/2014/01/08/adp-n7-screenshot-after.png'] 5 | related: ['/2013/07/binders-window-tokens.html', 6 | '/2012/06/app-force-close-honeycomb-ics.html', 7 | '/2012/08/exit-application-dialogs-are-evil-dont.html'] 8 | updated: '2014-03-11' 9 | --- 10 | A couple weeks ago, I began the ambitious task of rewriting this blog from scratch. 11 | Today, I'm happy to introduce a brand new look: one that is _cleaner_, _faster_, and more 12 | _responsive_. 13 | 14 | Several of the major changes are listed below. If this is your first time visiting this blog, you can find the old 15 | version of the site [here](http://androiddesignpatterns.blogspot.com) to use as a reference. 16 | 17 | 18 | 19 | + **Goodbye Blogger, hello Jekyll.** I've never been a huge fan of Blogger and was eager to 20 | try something new. After a bit of research I decided to give [Jekyll](http://jekyllrb.com/) 21 | a shot. Unlike Blogger, 22 | which dynamically parses content and templates on each request, Jekyll generates the entire 23 | website once beforehand to serve statically and is much more efficient as a result. It's a bit 24 | under-documented and I'm not a huge fan of [Liquid](https://github.com/Shopify/liquid) 25 | (the templating language Jekyll uses to process templates), but other than that I have no complaints. 26 | I'd take Jekyll over Blogger any day. 27 | 28 | + **100% open-source.** The source code powering this blog can 29 | be found on [GitHub](https://github.com/alexjlockwood/alexjlockwood.github.io), and may be used 30 | by others as the basis of their own blogging templates under the 31 | [MIT license](https://github.com/alexjlockwood/alexjlockwood.github.io/blob/master/README.md#license-and-copyright) 32 | (with the exception of the contents of the actual posts, of course :P). 33 | Given that Android Design Patterns wouldn't even exist without Android—one of the largest open-source 34 | projects in the world—making this blog 100% open-source seemed fitting. Another cool implication of an entirely 35 | open-source blog is that readers can propose corrections themselves by submitting pull requests 36 | to GitHub. 37 | 38 | + **Faster page load times.** Check out the [old version](http://androiddesignpatterns.blogspot.com) of this blog 39 | and see for yourself! [Page Speed](https://developers.google.com/speed/pagespeed/) reports an increase from 65/100 to 89/100 for mobile 40 | devices and 86/100 to 95/100 for desktop computers. 41 | 42 | + **Clean, responsive, and mobile-friendly.** First of all, I'd like to thank [+Shannon Lee](https://plus.google.com/116871425473751000037) 43 | for coming up with most of the new design. I consider myself a beginner at web design, so this couldn't have been done without her! 44 | That said, I definitely recommend checking out the site on your phone or tablet, as it's one of my favorite 45 | aspects of the new design. Below is a comparison of the old vs. new versions of the site on a Nexus 7: 46 | 47 |

48 |
49 | 50 | Website design before. 51 | 52 | Website design after. 53 |
54 |
55 | 56 | + **Disqus comments.** Picking a third-party commenting platform to use was difficult, as there weren't 57 | many options to choose from. I ended up choosing [Disqus](http://disqus.com/), as it was one of the few commenting systems that I could find 58 | that would correctly and reliably import my old comments from Blogger (spam detection is also a plus). One of the consequences of 59 | the migration, however, is that all old threaded comments are now unthreaded, meaning that most of the old 60 | comments are a bit of a mess right now. I plan on manually cleaning up these at some point in 61 | the future. Going forward, all new comments will thread normally. 62 | 63 | Let me know what you think of the new design in the comments below! Make sure to also leave any 64 | suggestions, criticisms, or feature requests too. The design will continue to be refined over time until 65 | it's perfect. :) 66 | -------------------------------------------------------------------------------- /_posts/2014-01-13-thread-scheduling-in-android.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Thread Scheduling in Android 4 | related: ['/2013/04/activitys-threads-memory-leaks.html', 5 | '/2012/10/sqlite-contentprovider-thread-safety.html', 6 | '/2013/07/binders-window-tokens.html'] 7 | --- 8 | This post will give an overview of how thread scheduling works in Android, and will briefly 9 | demonstrate how to explicitly set 10 | [thread priorities](http://developer.android.com/reference/android/os/Process.html) 11 | yourself to ensure that your application remains responsive even as multiple threads 12 | run in the background. 13 | 14 | For those who are unfamiliar with the term, a _thread scheduler_ is the part of the operating system 15 | in charge of deciding which threads in the system should run, when, and for how long. Android's thread 16 | scheduler uses two main factors to determine how threads are scheduled across the entire 17 | system: _nice values_ and _cgroups_. 18 | 19 | 20 | 21 | ### Nice values 22 | 23 | Similar to how they are used in Linux's completely fair scheduling policy, _nice values_ in Android 24 | are used as a measure of a thread's priority. Threads with a higher nice value (i.e., lower priority, 25 | as in they are being "nice" to other threads in the system) will run less often than 26 | those with lower nice values (i.e., higher priority). The two most important of these are the 27 | [default](http://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_DEFAULT) 28 | and [background](http://developer.android.com/reference/android/os/Process.html#THREAD_PRIORITY_BACKGROUND) 29 | thread priorities. Intuitively, thread priorities should be chosen 30 | inverse-proportionally to the amount of work the thread is expected to do: the more work the 31 | thread will do, the less favorable thread priority it should get so that it doesn't 32 | starve the system. For this reason, user interface threads (such as the main thread of a foreground `Activity`) 33 | are typically given a default priority, whereas background threads (such as a thread executing an `AsyncTask`) 34 | are typically given a background priority. 35 | 36 | Nice values are theoretically important because they help reduce background work that might otherwise 37 | interrupt the user interface. In practice, however, they alone are not sufficient. For example, consider 38 | twenty background threads and a single foreground thread driving the UI. Despite their low 39 | individual priorities, collectively the twenty background threads will still likely impact the performance 40 | of the single foreground thread, resulting in lag and hurting the user experience. Since at any given moment 41 | several applications could potentially have background threads waiting to run, the Android OS 42 | must somehow address these scenarios. Enter _cgroups_. 43 | 44 | ### Cgroups 45 | 46 | To address this problem, Android enforces an even stricter foreground vs. background scheduling policy 47 | using Linux [_cgroups_](http://en.wikipedia.org/wiki/Cgroups) (control groups). Threads with 48 | background priorities are implicitly moved into a background cgroup, where they are 49 | limited to only a small percentage1 of the available 50 | CPU if threads in other groups are busy. This separation allows background threads to make some 51 | forward progress, without having enough of an impact on the foreground threads to be visible 52 | to the user. 53 | 54 | In addition to automatically assigning low-priority threads to the background cgroup, Android ensures that all 55 | threads belonging to applications not currently running in the foreground are implicitly moved to the background cgroup as well. This automatic assignment of application threads to cgroups ensures that the current foreground 56 | application thread will always be the priority, regardless of how many applications are running in the background. 57 | 58 | ### Setting Priorities with `Process#setThreadPriority(int)` 59 | 60 | For the most part, the Android APIs already assign worker threads a background priority for you 61 | (for example, see the source code for 62 | [`HandlerThread`](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/HandlerThread.java) 63 | and [`AsyncTask`](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/AsyncTask.java)). It is important to remember, however, that this will not always be the case. 64 | `Thread`s and `ExecutorService`s that are instantiated on the main UI thread, for example, 65 | will inherit a default, foreground priority, making lag more likely and possibly hurting 66 | the application's performance. In these cases, you should _always_ remember to set the thread's 67 | priority by calling 68 | `Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)` 69 | before the `Thread` is run. Doing so is straightforward, as shown in the example below: 70 | 71 | ```java 72 | new Thread(new Runnable() { 73 | @Override 74 | public void run() { 75 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 76 | 77 | // ... 78 | } 79 | }).start(); 80 | ``` 81 | 82 | As always, thanks for reading, and leave a comment if you have any questions. Don't forget to +1 this blog post too! 83 | 84 |
85 | 86 | 1 This percentage was 5% at the time of this writing, though it is possible that this value could change in the future. As of Android 4.4.2, cgroup mount points are created and initialized at boot-up in [`/system/core/rootdir/init.rc`](https://android.googlesource.com/platform/system/core/+/android-sdk-4.4.2_r1/rootdir/init.rc) (see lines 100-123). -------------------------------------------------------------------------------- /_posts/2018-11-13-android-studio-svg-to-vector-cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "How to use Android Studio's SVG-to-VectorDrawable converter from the command line" 4 | date: 2018-11-13 5 | permalink: /2018/11/android-studio-svg-to-vector-cli.html 6 | related: ['/2013/08/fragment-transaction-commit-state-loss.html', 7 | '/2016/11/introduction-to-icon-animation-techniques.html', 8 | '/2016/08/contextcompat-getcolor-getdrawable.html'] 9 | --- 10 | 11 | 12 | 13 | Since the very beginning, one of the most annoying aspects of using `VectorDrawable`s on Android has been the lack of a reliable SVG converter. Google has recently made huge strides towards improving Android Studio's SVG-to-`VectorDrawable` tool in order to improve this experience. 14 | 15 | However, there has always been one major issue for me: the tool doesn't support batch conversion of SVGs and can't be invoked from the command line. Working at Lyft, it's not uncommon for me to need to convert hundreds of SVGs into `VectorDrawable`s at a time. Having to go through Android Studio's GUI in order to convert each SVG one-by-one is simply not realistic. 16 | 17 | 18 | 19 | Well, this weekend my good buddy [Nick Butcher](https://twitter.com/crafty) taught me how to build the tool as a binary that can be executed from the command line, and I'm so excited about it that I had to share it with the world! :) 20 | 21 | ## Where can I download it? 22 | 23 | I anticipate many won't want to go through the trouble of building these binaries from scratch, so I built them myself and hosted them on [Google Drive here](https://j.mp/svg-to-vector-google-drive). 24 | 25 | ## How do I run it? 26 | 27 | The following command will convert all SVGs in a directory called `svgs/`, convert them all into `VectorDrawable`s, and write them to a directory called `vectors/`. Note that both directories must exist beforehand. 28 | 29 | ``` 30 | ./vd-tool -c -in svgs/ -out vectors/ 31 | ``` 32 | 33 | ## How do I build it? 34 | 35 | In case you want to build these yourself, here's how I did it. 36 | 37 | First, follow the [Downloading the Source](https://source.android.com/source/downloading.html) guide to install and set up the `repo` tool, but instead of running the listed `repo` commands to initialize the repository, run the folowing: 38 | 39 | ``` 40 | repo init -u https://android.googlesource.com/platform/manifest -b 41 | ``` 42 | 43 | At the time of this writing, the most recent Android Studio branch was `studio-3.2.1`. This will obviously change over time as newer versions of Android Studio are released. 44 | 45 | Now that your repository is set to pull only what you need for building and running the tool, download the code using the following command (you might want to grab a coffee or something too, as this command might take a while to complete): 46 | 47 | ``` 48 | repo sync -j8 -c 49 | ``` 50 | 51 | Finally, execute the following to build the binaries: 52 | 53 | ``` 54 | cd ./tools/base 55 | ../gradlew publishLocal 56 | ``` 57 | 58 | Once it completes, you should find the binaries in a `/out/build/base/vector-drawable-tool/build/distributions/vd-tool.zip` file. Unzip it and it will extract a `/bin` directory that contains binaries compatible with Mac OS X, Linux, and Windows. 59 | 60 | ## How do I report bugs? 61 | 62 | Now for the most important part of this blog post... 63 | 64 | Please, please, **please** file bugs if you discover SVGs that don't convert properly! File the bugs in the [Android Studio public issue tracker](https://issuetracker.google.com/issues?q=componentid:192708%20status:open). Here's [an example bug](https://issuetracker.google.com/issues/119372339) I recently reported if you need a template to go by. 65 | 66 | My goal is to make the Android Studio converter the most reliable SVG-to-`VectorDrawable` converter out there. So if and when you file a bug, feel free to [hit me up on Twitter](https://twitter.com/alexjlockwood) with a link to the report and I'll do my best to ensure the bug is fixed as quickly as possible! 67 | 68 | Happy converting! 69 | -------------------------------------------------------------------------------- /about/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: About Me 4 | id: 'about/index' 5 | --- 6 |

About Me

7 | 8 | A photo of {{ site.author.name }} 9 | 10 |

Hi, my name's Alex. I'm a passionate Android developer and a huge fan of design patterns.

11 | 12 |

As a contributor to several open-source projects and an active user 13 | on StackOverflow, I've encountered too many applications that suffer due to poor 14 | code design. Organizing the code that makes up your Android project can be a challenge, 15 | but it is essential when writing an application that can be easily extended and maintained 16 | in the future.

17 | 18 |

The goal of this blog is simple: to encourage proper coding practices when it comes 19 | to Android development. The blog posts range from in-depth tutorials to casual rants 20 | on Android-related topics. I hope that each post will give you with a better understanding 21 | as to why proper code design in Android development is so important.

22 | 23 |

If you have any questions, don't hesitate to leave a comment! 24 | Feel free to shoot me an email at 25 | ale...@androiddesignpatterns.com 26 | if you have any suggestions about the site. You can add me to your circles on 27 | Google+ 28 | as well!

29 | 30 |

Cheers,
31 | Alex

32 | -------------------------------------------------------------------------------- /adp.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": ".", 6 | "folder_exclude_patterns": ["node_modules"], 7 | "file_exclude_patterns": ["*~", ".jekyll-metadata", ] 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /archives/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Archives 4 | id: 'archives/index' 5 | --- 6 |

Archives

7 | 8 |
9 | {% for post in site.posts %} 10 | {% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %} 11 | {% capture next_year %}{{ post.previous.date | date: "%Y" }}{% endcapture %} 12 | 13 | {% if forloop.first %} 14 |

{{ this_year }}

15 |
    16 | {% endif %} 17 | 18 |
  • {{ post.date | date: "%b %-d" }}{{ post.title }}
  • 19 | 20 | {% if forloop.last %} 21 |
22 | {% elsif this_year != next_year %} 23 | 24 |

{{ next_year }}

25 |
    26 | {% endif %} 27 | {% endfor %} 28 |
29 | -------------------------------------------------------------------------------- /assets/fonts/social-media.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/fonts/social-media.eot -------------------------------------------------------------------------------- /assets/fonts/social-media.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2014 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/fonts/social-media.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/fonts/social-media.ttf -------------------------------------------------------------------------------- /assets/fonts/social-media.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/fonts/social-media.woff -------------------------------------------------------------------------------- /assets/images/2048_icon_72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/2048_icon_72x72.png -------------------------------------------------------------------------------- /assets/images/android1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/android1x.png -------------------------------------------------------------------------------- /assets/images/android2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/android2x.png -------------------------------------------------------------------------------- /assets/images/favicon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/favicon.psd -------------------------------------------------------------------------------- /assets/images/favicon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/favicon1024.png -------------------------------------------------------------------------------- /assets/images/favicon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/favicon128.png -------------------------------------------------------------------------------- /assets/images/favicon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/favicon16.png -------------------------------------------------------------------------------- /assets/images/favicon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/favicon256.png -------------------------------------------------------------------------------- /assets/images/favicon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/favicon32.png -------------------------------------------------------------------------------- /assets/images/favicon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/favicon512.png -------------------------------------------------------------------------------- /assets/images/favicon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/favicon64.png -------------------------------------------------------------------------------- /assets/images/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/ic_launcher.png -------------------------------------------------------------------------------- /assets/images/nexus6_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/nexus6_frame.png -------------------------------------------------------------------------------- /assets/images/posts/2012/08/07/back-button-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2012/08/07/back-button-pressed.png -------------------------------------------------------------------------------- /assets/images/posts/2012/08/07/dialog-showing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2012/08/07/dialog-showing.png -------------------------------------------------------------------------------- /assets/images/posts/2012/09/16/app-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2012/09/16/app-screenshot.png -------------------------------------------------------------------------------- /assets/images/posts/2012/09/16/eclipse-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2012/09/16/eclipse-screenshot.png -------------------------------------------------------------------------------- /assets/images/posts/2012/09/16/google-play-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2012/09/16/google-play-badge.png -------------------------------------------------------------------------------- /assets/images/posts/2013/04/15/activity-leak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2013/04/15/activity-leak.png -------------------------------------------------------------------------------- /assets/images/posts/2013/04/15/leaky-threads-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2013/04/15/leaky-threads-screenshot.png -------------------------------------------------------------------------------- /assets/images/posts/2013/04/29/worker-fragments-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2013/04/29/worker-fragments-screenshot.png -------------------------------------------------------------------------------- /assets/images/posts/2014/01/08/adp-n7-screenshot-after-resized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2014/01/08/adp-n7-screenshot-after-resized.png -------------------------------------------------------------------------------- /assets/images/posts/2014/01/08/adp-n7-screenshot-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2014/01/08/adp-n7-screenshot-after.png -------------------------------------------------------------------------------- /assets/images/posts/2014/01/08/adp-n7-screenshot-before-resized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2014/01/08/adp-n7-screenshot-before-resized.png -------------------------------------------------------------------------------- /assets/images/posts/2014/01/08/adp-n7-screenshot-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2014/01/08/adp-n7-screenshot-before.png -------------------------------------------------------------------------------- /assets/images/posts/2016/08/07/rant7-contextcompat-examples-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2016/08/07/rant7-contextcompat-examples-19.png -------------------------------------------------------------------------------- /assets/images/posts/2016/08/07/rant7-contextcompat-examples-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2016/08/07/rant7-contextcompat-examples-23.png -------------------------------------------------------------------------------- /assets/images/posts/2016/08/11/themed-buttons-disabled-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2016/08/11/themed-buttons-disabled-19.png -------------------------------------------------------------------------------- /assets/images/posts/2016/08/11/themed-buttons-disabled-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2016/08/11/themed-buttons-disabled-23.png -------------------------------------------------------------------------------- /assets/images/posts/2016/08/11/themed-buttons-enabled-pressed-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2016/08/11/themed-buttons-enabled-pressed-19.png -------------------------------------------------------------------------------- /assets/images/posts/2016/08/11/themed-buttons-enabled-pressed-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2016/08/11/themed-buttons-enabled-pressed-23.png -------------------------------------------------------------------------------- /assets/images/posts/2016/08/11/themed-buttons-enabled-unpressed-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2016/08/11/themed-buttons-enabled-unpressed-19.png -------------------------------------------------------------------------------- /assets/images/posts/2016/08/11/themed-buttons-enabled-unpressed-23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2016/08/11/themed-buttons-enabled-unpressed-23.png -------------------------------------------------------------------------------- /assets/images/posts/2018/01/24/nsv-overlay-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2018/01/24/nsv-overlay-small.png -------------------------------------------------------------------------------- /assets/images/posts/2018/01/24/nsv-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2018/01/24/nsv-overlay.png -------------------------------------------------------------------------------- /assets/images/posts/2018/01/24/rv-overlay-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2018/01/24/rv-overlay-small.png -------------------------------------------------------------------------------- /assets/images/posts/2018/01/24/rv-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2018/01/24/rv-overlay.png -------------------------------------------------------------------------------- /assets/images/posts/2018/01/24/sample-app-layouts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2018/01/24/sample-app-layouts.jpg -------------------------------------------------------------------------------- /assets/images/posts/2018/01/24/sample-app-layouts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/posts/2018/01/24/sample-app-layouts.png -------------------------------------------------------------------------------- /assets/images/profile-photo-gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/profile-photo-gopher.png -------------------------------------------------------------------------------- /assets/images/profile-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/images/profile-photo.jpg -------------------------------------------------------------------------------- /assets/images/shapeshifter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/04/news-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/04/news-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/04/news-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/04/news-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/04/news-opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/04/news-opt.png -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/04/news-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/04/news-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/04/news.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/04/news.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/04/trivial-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/04/trivial-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/04/trivial-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/04/trivial-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/04/trivial-opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/04/trivial-opt.png -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/04/trivial-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/04/trivial-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/04/trivial.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/04/trivial.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/15/games-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/15/games-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/15/games-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/15/games-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/15/games-opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/15/games-opt.png -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/15/games-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/15/games-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/15/games.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/15/games.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/15/webview-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/15/webview-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/15/webview-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/15/webview-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/15/webview-opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/15/webview-opt.png -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/15/webview-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/15/webview-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2014/12/15/webview.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2014/12/15/webview.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2015/01/12/music-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/01/12/music-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2015/01/12/music-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/01/12/music-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2015/01/12/music-opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/01/12/music-opt.png -------------------------------------------------------------------------------- /assets/videos/posts/2015/01/12/music-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/01/12/music-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2015/01/12/music.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/01/12/music.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2015/01/12/overlay-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/01/12/overlay-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2015/01/12/overlay-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/01/12/overlay-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2015/01/12/overlay-opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/01/12/overlay-opt.png -------------------------------------------------------------------------------- /assets/videos/posts/2015/01/12/overlay-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/01/12/overlay-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2015/01/12/overlay.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/01/12/overlay.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2015/03/09/postpone-bug-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/03/09/postpone-bug-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2015/03/09/postpone-bug-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/03/09/postpone-bug-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2015/03/09/postpone-bug-opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/03/09/postpone-bug-opt.png -------------------------------------------------------------------------------- /assets/videos/posts/2015/03/09/postpone-bug-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/03/09/postpone-bug-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2015/03/09/postpone-bug.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2015/03/09/postpone-bug.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/cheesesquare-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/cheesesquare-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/cheesesquare-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/cheesesquare-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/cheesesquare-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/cheesesquare-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/cheesesquare.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/cheesesquare.m4v -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/cheesesquare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/cheesesquare.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/expeditions-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/expeditions-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/expeditions-sample-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/expeditions-sample-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/expeditions-sample-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/expeditions-sample-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/expeditions-sample-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/expeditions-sample-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/expeditions-sample.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/expeditions-sample.m4v -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/expeditions-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/expeditions-sample.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/expeditions.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/expeditions.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/expeditions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/expeditions.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/fling-bug-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/fling-bug-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/fling-bug-orig.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/fling-bug-orig.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/fling-bug.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/fling-bug.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/fling-bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/fling-bug.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/nested-scrolling-bugs1-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/nested-scrolling-bugs1-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/nested-scrolling-bugs1-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/nested-scrolling-bugs1-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/nested-scrolling-bugs1-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/nested-scrolling-bugs1-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/nested-scrolling-bugs1.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/nested-scrolling-bugs1.m4v -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/nested-scrolling-bugs1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/nested-scrolling-bugs1.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/nested-scrolling-bugs2-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/nested-scrolling-bugs2-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/nested-scrolling-bugs2-opt.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/nested-scrolling-bugs2-opt.ogv -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/nested-scrolling-bugs2-opt.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/nested-scrolling-bugs2-opt.webm -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/nested-scrolling-bugs2.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/nested-scrolling-bugs2.m4v -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/nested-scrolling-bugs2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/nested-scrolling-bugs2.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/poster-cheesesquare.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/poster-cheesesquare.jpg -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/poster-cheesesquare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/poster-cheesesquare.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/poster-expeditions-sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/poster-expeditions-sample.jpg -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/poster-expeditions-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/poster-expeditions-sample.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/poster-nested-scrolling-bugs1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/poster-nested-scrolling-bugs1.jpg -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/poster-nested-scrolling-bugs1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/poster-nested-scrolling-bugs1.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/poster-nested-scrolling-bugs2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/poster-nested-scrolling-bugs2.jpg -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/poster-nested-scrolling-bugs2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/poster-nested-scrolling-bugs2.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/sample-app-fling-actual-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/sample-app-fling-actual-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/sample-app-fling-actual.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/sample-app-fling-actual.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/sample-app-fling-expected-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/sample-app-fling-expected-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/sample-app-fling-expected.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/sample-app-fling-expected.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/sample-app-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/sample-app-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/sample-app-orig.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/sample-app-orig.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/sample-app.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/sample-app.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/scroll-bug-opt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/scroll-bug-opt.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/scroll-bug-orig.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/scroll-bug-orig.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/01/24/scroll-bug.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/01/24/scroll-bug.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/introducing-kyrie-animals.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/introducing-kyrie-animals.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/introducing-kyrie-animals.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/introducing-kyrie-animals.webm -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/introducing-kyrie-heartbreak.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/introducing-kyrie-heartbreak.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/introducing-kyrie-heartbreak.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/introducing-kyrie-heartbreak.webm -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/introducing-kyrie-polygons.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/introducing-kyrie-polygons.mp4 -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/introducing-kyrie-polygons.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/introducing-kyrie-polygons.webm -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/poster-introducing-kyrie-animals.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/poster-introducing-kyrie-animals.jpg -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/poster-introducing-kyrie-animals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/poster-introducing-kyrie-animals.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/poster-introducing-kyrie-heartbreak.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/poster-introducing-kyrie-heartbreak.jpg -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/poster-introducing-kyrie-heartbreak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/poster-introducing-kyrie-heartbreak.png -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/poster-introducing-kyrie-polygons.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/poster-introducing-kyrie-polygons.jpg -------------------------------------------------------------------------------- /assets/videos/posts/2018/03/06/poster-introducing-kyrie-polygons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/assets/videos/posts/2018/03/06/poster-introducing-kyrie-polygons.png -------------------------------------------------------------------------------- /css/README.txt: -------------------------------------------------------------------------------- 1 | The following modifications were made to Material Design Lite's default CSS: 2 | 3 | 1. Updated the order of the preferred fonts to: 4 | 5 | $preferred_font: 'Roboto', sans-serif, 'Helvetica', 'Arial' !default; 6 | 7 | 2. Updated the default accent colors to: 8 | 9 | $color-primary: "96,144,0" !default; 10 | $color-primary-dark: "96,144,0" !default; 11 | $color-accent: "96,144,0" !default; 12 | 13 | 3. Commented out blockquote typography rule. 14 | -------------------------------------------------------------------------------- /css/all.css: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | {% include normalize.css %} 4 | {% include syntax.css %} 5 | {% include social.css %} 6 | {% include style.min.css %} 7 | {% include fonts.css %} 8 | -------------------------------------------------------------------------------- /css/posts/2016/11/29/style.css: -------------------------------------------------------------------------------- 1 | /* TODO(alockwood): figure out why compass/css3 is apparently not found? 2 | @import "compass/css3"; */ 3 | 4 | .svgDemoContainer { 5 | background-color: #f5f5f5; 6 | border: 1px solid #ccc; 7 | padding: 16px; 8 | } 9 | 10 | .svgDemoGraphic { 11 | width: 240px; 12 | height: 240px; 13 | } 14 | 15 | .svgTrimPathDemoGraphic { 16 | width: 300px; 17 | height: 300px; 18 | } 19 | 20 | .svgTransformPathsDemoList { 21 | margin-top: 16px; 22 | list-style: none; 23 | padding-left: 0; 24 | } 25 | 26 | .svgBasicDemoPathInstructionList { 27 | margin-top: 16px; 28 | } 29 | 30 | .svgBasicDemoPathInstruction { 31 | margin: 0px; 32 | } 33 | 34 | .flex-container { 35 | padding: 0; 36 | margin: 0; 37 | list-style: none; 38 | display: -webkit-box; 39 | display: -moz-box; 40 | display: -ms-flexbox; 41 | display: -webkit-flex; 42 | display: flex; 43 | -webkit-flex-flow: row wrap; 44 | justify-content: space-around; 45 | } 46 | 47 | .flex-item { 48 | padding: 5px; 49 | margin-top: 10px; 50 | } 51 | 52 | .svgDemoCheckboxContainer { 53 | padding: 16px; 54 | } 55 | 56 | 57 | /* Linear progress bar demo. */ 58 | 59 | #progressBarContainer { 60 | padding: 16px; 61 | } 62 | 63 | #progressBar { 64 | max-width: 360px; 65 | height: 4px; 66 | overflow: hidden; 67 | position: relative; 68 | background-color: rgba(96, 144, 0, 0.3); 69 | } 70 | 71 | #progressBarInnerRect1, 72 | #progressBarInnerRect2 { 73 | background: #690; 74 | } 75 | 76 | #progressBarOuterRect1, 77 | #progressBarOuterRect2, 78 | #progressBarInnerRect1, 79 | #progressBarInnerRect2 { 80 | height: 4px; 81 | position: absolute; 82 | width: 288px; 83 | } 84 | 85 | 86 | /* Trim path interactive demo. */ 87 | 88 | #ic_line_path { 89 | padding-top: 16px; 90 | padding-left: 16px; 91 | padding-right: 16px; 92 | } 93 | 94 | .sliderContainer { 95 | padding: 16px; 96 | } 97 | 98 | .slider { 99 | margin-top: 8px; 100 | display: inline-block; 101 | } 102 | 103 | .sliderInput { 104 | width: 300px; 105 | } 106 | 107 | .sliderTextContainer { 108 | margin-top: 8px; 109 | margin-bottom: 8px; 110 | display: block; 111 | } 112 | 113 | .sliderText { 114 | display: inline-block; 115 | } 116 | 117 | 118 | /* Basic properties demo. */ 119 | 120 | .basic_path_property_play_inactive { 121 | -webkit-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 122 | -moz-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 123 | -o-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 124 | -ms-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 125 | transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 126 | fill: #FF9800; 127 | } 128 | 129 | .basic_path_property_play_active { 130 | -webkit-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 131 | -moz-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 132 | -o-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 133 | -ms-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 134 | transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 135 | fill: #4285F4; 136 | } 137 | 138 | .basic_path_property_pause_inactive { 139 | -webkit-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 140 | -moz-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 141 | -o-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 142 | -ms-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 143 | transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 144 | stroke-width: 40; 145 | stroke: #0F9D58; 146 | fill: none; 147 | } 148 | 149 | .basic_path_property_pause_active { 150 | -webkit-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 151 | -moz-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 152 | -o-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 153 | -ms-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 154 | transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 155 | stroke-width: 20; 156 | stroke: #0F9D58; 157 | fill: none; 158 | } 159 | 160 | .basic_path_property_record_inactive { 161 | -webkit-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 162 | -moz-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 163 | -o-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 164 | -ms-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 165 | transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 166 | fill: #DB4437; 167 | fill-opacity: 1; 168 | } 169 | 170 | .basic_path_property_record_active { 171 | -webkit-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 172 | -moz-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 173 | -o-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 174 | -ms-transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 175 | transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1.0); 176 | fill: #DB4437; 177 | fill-opacity: 0.5; 178 | } 179 | 180 | .basic_path_property_xy_point { 181 | fill: #000; 182 | } 183 | 184 | .delightIconFillPath { 185 | fill: #000; 186 | } 187 | 188 | .delightIconStrokePath { 189 | fill: none; 190 | stroke: #000; 191 | } 192 | 193 | .delightIconHighlightPath { 194 | fill: none; 195 | stroke: #c00; 196 | visibility: hidden; 197 | } 198 | 199 | .delightIconSearchToBackStrokePath { 200 | fill: none; 201 | stroke: #000; 202 | stroke-width: 2; 203 | } 204 | 205 | .delightIconSearchToBackStrokePathDebug { 206 | fill: none; 207 | stroke: #000; 208 | stroke-width: 2; 209 | stroke-opacity: 0.3; 210 | visibility: hidden; 211 | } 212 | 213 | .delightIconHandwritingStrokePath { 214 | fill: none; 215 | stroke: #5C6BC0; 216 | stroke-width: 3; 217 | stroke-linejoin: round; 218 | stroke-linecap: round; 219 | } 220 | 221 | .delightIconHandwritingStrokePathDebug { 222 | fill: none; 223 | stroke: #000; 224 | stroke-width: 3; 225 | stroke-linejoin: round; 226 | stroke-linecap: round; 227 | stroke-opacity: 0.3; 228 | visibility: hidden; 229 | } 230 | 231 | .delightIconFingerPrintStrokePath { 232 | fill: none; 233 | stroke: #000; 234 | stroke-linecap: round; 235 | stroke-width: 1.45; 236 | } 237 | 238 | .delightIconFingerPrintStrokePathDebug { 239 | fill: none; 240 | stroke: #000; 241 | stroke-linecap: round; 242 | stroke-width: 1.45; 243 | stroke-opacity: 0.3; 244 | visibility: hidden; 245 | } 246 | 247 | .delightIconIo16StrokePath { 248 | fill: none; 249 | stroke-width: 5; 250 | } 251 | 252 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexjlockwood/AndroidDesignPatterns/0dfff8f49e9d56d25ea5ff95e010f5be324e4528/favicon.ico -------------------------------------------------------------------------------- /feed.atom: -------------------------------------------------------------------------------- 1 | --- 2 | layout: null 3 | title: Atom Feed 4 | --- 5 | 6 | 7 | {{ site.name | xml_escape }} 8 | 9 | 10 | {{ site.baseurl }}/ 11 | 12 | {{ site.author.name | xml_escape }} 13 | 14 | {{ site.time | date_to_xmlschema }} 15 | {% for post in site.posts %} 16 | 17 | {{ post.title | xml_escape }} 18 | 19 | {{ site.baseurl }}{{ post.url }} 20 | {{ post.date | date_to_xmlschema }} 21 | {% if post.updated == null %}{{ post.date | date_to_xmlschema }}{% else %}{{ post.updated | date_to_xmlschema }}{% endif %} 22 | {{ post.excerpt | xml_escape }} 23 | {{ post.content | xml_escape }} 24 | 25 | {% endfor %} 26 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const webpack = require('webpack-stream'); 3 | const gp_childProcess = require('child_process'); 4 | const $ = require('gulp-load-plugins')({'camelize': true}); 5 | 6 | gulp.task('default', ['less', 'js', 'serve']); 7 | 8 | gulp.task('less', function() { 9 | return gulp.src('_less/style.less') 10 | .pipe($.less()) 11 | .pipe(gulp.dest('_includes')) 12 | .pipe($.cleanCss()) 13 | .pipe($.rename({ 14 | suffix: '.min' 15 | })) 16 | .pipe(gulp.dest('_includes')); 17 | }); 18 | 19 | gulp.task('js', function() { 20 | return gulp.src('_js/src/**/*.js') 21 | .pipe($.concat('concat.js')) 22 | .pipe(gulp.dest('_js/build')) 23 | .pipe(webpack({ 24 | output: { 25 | path: __dirname, 26 | filename: "all.js" 27 | }, 28 | module: { 29 | loaders: [] 30 | } 31 | })) 32 | .pipe($.uglify()) 33 | .pipe($.rename({ extname: '.min.js' })) 34 | .pipe(gulp.dest('scripts')); 35 | }); 36 | 37 | gulp.task('serve', function(done) { 38 | return gp_childProcess.spawn( 39 | 'bundle', 40 | ['exec', 'jekyll', 'serve', '--baseurl=', '--incremental'], 41 | { stdio: 'inherit' }) 42 | .on('close', done); 43 | }); 44 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Android Design Patterns 4 | description: 'Android Design Patterns is a website for developers who wish to better understand the Android application framework. The tutorials here emphasize proper code design and project maintainability.' 5 | --- 6 | {% for post in paginator.posts %} 7 |
8 |
9 |
10 | {{ post.date | date: "%b %-d %Y" }} 11 |
12 |
13 |
14 |

{{ post.title | escape }}

15 | {{ post.content | split:"" | last | split:"" | first }} 16 | 19 |
20 |
21 |
22 | {% if forloop.index0 == 1 %} 23 | 24 | 34 | {% endif %} 35 | {% endfor %} 36 | {% if paginator.previous_page or paginator.next_page %} 37 |
38 | {% if paginator.previous_page %} 39 | {% if paginator.previous_page == 1 %} 40 | 41 | {% else %} 42 | 43 | {% endif %} 44 | {% endif %} 45 | {% if paginator.next_page %} 46 | 47 | {% endif %} 48 |
49 | {% endif %} 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "android-design-patterns", 3 | "version": "1.0.0", 4 | "description": "Android Design Patterns blog source code.", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/alexjlockwood/alexjlockwood.github.io.git" 12 | }, 13 | "keywords": [ 14 | "android", 15 | "design", 16 | "patterns", 17 | "development", 18 | "blog" 19 | ], 20 | "author": "Alex Lockwood", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/alexjlockwood/alexjlockwood.github.io/issues" 24 | }, 25 | "homepage": "https://github.com/alexjlockwood/alexjlockwood.github.io#readme", 26 | "devDependencies": { 27 | "bezier-easing": "^2.0.3", 28 | "gulp": "^3.8.8", 29 | "gulp-clean-css": "^3.9.2", 30 | "gulp-concat": "^2.6.1", 31 | "gulp-less": "^3.4.0", 32 | "gulp-load-plugins": "^1.4.0", 33 | "gulp-rename": "^1.2.2", 34 | "gulp-uglify": "^3.0.0", 35 | "path-data-polyfill": "^1.0.0", 36 | "pump": "^2.0.1", 37 | "web-animations-js": "^2.3.1", 38 | "webpack-stream": "^4.0.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | User-agent: * 4 | Disallow: /godesignpatterns/ 5 | 6 | Sitemap: {{ site.baseurl }}/sitemap.xml 7 | -------------------------------------------------------------------------------- /scripts/disqus-lazy-load.js: -------------------------------------------------------------------------------- 1 | var comments = document.getElementById('disqus_thread'); 2 | var disqus_loaded = false; 3 | 4 | function load_disqus() { 5 | var disqus_shortname = 'androiddesignpatterns'; 6 | disqus_loaded = true; 7 | var dsq = document.createElement('script'); 8 | dsq.type = 'text/javascript'; 9 | dsq.async = true; 10 | dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; 11 | (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); 12 | } 13 | 14 | // get offset of an object 15 | function findTop(obj) { 16 | var curtop = 0; 17 | if (obj.offsetParent) { 18 | do { 19 | curtop += obj.offsetTop; 20 | } while (obj = obj.offsetParent); 21 | return curtop; 22 | } 23 | } 24 | 25 | if (window.location.hash.indexOf('#comment') >= 0) { 26 | load_disqus(); 27 | } 28 | 29 | if (comments) { 30 | var commentsOffset = findTop(comments); 31 | window.onscroll = function() { 32 | if (!disqus_loaded && window.pageYOffset > commentsOffset - 1500) { 33 | load_disqus(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /sitemap.xml: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | {{ site.baseurl }} 7 | {{ site.time | date_to_xmlschema }} 8 | monthly 9 | 1.0 10 | 11 | {% for post in site.posts %} 12 | {{ site.baseurl }}{{ post.url }} 13 | {% if post.updated == null %}{{ post.date | date: '%Y-%m-%d' }}{% else %}{{ post.updated | date: '%Y-%m-%d' }}{% endif %} 14 | monthly 15 | 0.9 16 | {% endfor %} 17 | {% for page in site.pages %} 18 | 19 | {{ site.baseurl }}{{ page.url }} 20 | {{ site.time | date_to_xmlschema }} 21 | monthly 22 | 0.8 23 | 24 | {% endfor %} 25 | 26 | --------------------------------------------------------------------------------