Bye, PremoFM

sad_jordan_logo

I will be ending PremoFM development and shutting the servers down on Sunday, February 14th..  I will be destroying all data, including user data, at this same time.  Users can export their current subscriptions by going to Settings > Export to OPML.  You can use the OPML file to import your podcasts into other great podcast apps like Pocket Casts (my favorite), Beyond Pod, and Player FM.

Why?  Purely, the model I adopted with PremoFM was unsustainable (and an important lesson learned).  I decided to go the free + patronage route.  In order for this to work, I’d need a ton of users, because, naturally, only a small percentage of users would opt to be a patron.  I only had 176 installs as of today, February 10, not enough by a longshot.  I’d need to invest a ton of time and money in marketing.  As a side project, PremoFM was unable to get that level of attention from me.  There are millions of apps in the Play Store, so marketing is an overwhelming requirement.  It’s rare for an app to organically get noticed or go viral.  Despite repeated attempts to have my app reviewed by various Android / Tech blogs and websites, I was mostly unsuccessful in generating sustainable attention for PremoFM.  I originally sought out to create the podcast app I would want to use and I “hoped” others would too, but I didn’t spend enough time on the business model or differentiation.  So in a sea of tens of other more well known, high quality podcast apps, PremoFM failed to acquire a ton of users.

It wasn’t all a loss.  Building PremoFM allowed me to gain a truly deep understanding of Android development and APIs. Some things I gained experience with include playing media, Google Cast APIs, Google Cloud Messenger, ContentProviders, Material Design, Services, JobSchedulers, etc.  I was able to use this experience to gain full-time employment with WillowTree Apps last fall.  I even gave a few decent talks on MediaSession APIs, added to Android 5.0.  I even gained a ton of experience spinning up servers in a hosted environments, setting up an OpenVPN network, and writing threaded XML retrieval, parsing, and processing logic.  I’d never again write threaded processes in Java ever again, but now I know.  I also achieved a 4.76 out of 5 star rating, which I’m kinda proud of, despite the low install numbers.

Screenshot_20160210-211744

Moving on, I will be completely open sourcing the app, backend automation, API, and scripts in the next few weeks.  If you like the app enough and you are a developer, you *could* mate the parsing engine with the Android app, since it’s all Java and uses PremoFM without the expensive backend.

R.I.G. PremoFM

[R.I.G. = Rest in GitHub]

OMG Kotlin

I finally got around to watching Jake Wharton’s talk on how developers can use Kotlin to build their Android apps.  It’s a must watch.  For Android developers.

My initial thoughts on Kotlin were:

  1. Hmmm, reminds my of Swift (of Apple fame).
  2. Oh my goodness, take my money.
  3. Oh, oooh, ooooooooooooh.
  4. Nice.
  5. Kotlin is compiled to Java bytecode.  You still need to have reasonably deep knowledge of the compiler to avoid creating unnecessary objects or have inner classes hold onto references to the outer class, fail…
  6. …but, you need to have reasonably deep knowledge of the Java compiler anyway, even if you are writing just Java code (kind of invalidating point 5).
  7. This is amazing, this is the future.

One thing I have begun to realize as I learn Swift and now have seen Kotlin.  Java is VERY verbose with tons of ceremony.  Swift, Kotlin, and other similar languages are doing a good job stripping that all away and making coding genuinely fun and clean.

Super Artificial Intelligence

Last week, I read a two part piece, by Tim Urban, describing super artificial intelligence, it’s origins and inevitable ramifications on human life.

The AI Revolution: The Road to Super Intelligence

The AI Revolution: Our Immortality or Extinction

Tim does a very good job of putting everything in context, whereas, we might not be able to envision a future incredibly enhanced by super AI (most experts agree we’ll see super AI in the lifetime of the average millennial), but that’s perfectly human.  I certainly had a hard time wrapping my head around some of these notions.  He describes artificial intelligence that will appear to have been a sudden discovery, but we’ve been chipping away at it slowly and methodically (but faster and faster as technology progresses).

ibm20ps220model2030-11382846

Kind of on a related note, I look at where technology was when I used a computer (that IBM PS/2 Model 30) for the first time, then fast forward to today, an incredible amount of progress that is very easy to be taken for granted.  Technology has progressed rapidly over the past 20-30 years, and it’s advancements, when summed up, are pretty incredible.  I carry a device in my pocket (and on my wrist) that has more computing power than a lot of the computers I’ve owned, combined.  

The advancement of artificial intelligence will be like this.  Seemingly tiny iterations that we all just get used to like Gmail spam filtering, Google Now, Google Photos face detection, Siri, credit card fraud detection systems, Google Maps navigation, music suggestion / playlist creation, etc.  One day, we’ll have super artificial intelligence.  Now I don’t know what it’ll look like.  Will my Google Maps navigation all of a sudden just become awesome?  Or will Google market their branded super artificial intelligence solution.  Who knows?

Consequently, today Google’s DeepMind division made a particularly big advancement in artificial intelligence with AlphaGo, a computer that can beat the world’s best Go players.
¯\_(ツ)_/¯

PremoFM 1.2 – Introducing Pinning, OPML support, iTunes links, and more Material

web_hi_res_512

PremoFM, Android’s freshest podcast app, just leveled up with PremoFM 1.2 with a lot more awesome.  It’s available in Google Play now!

What’s new?

Pinning allows you save episodes to your device without subscribing to the podcast.  Want to check out Tim Ferriss Podcast w/ Jamie Foxx, but don’t want to subscribe?  Pin that episode to your device and you can listen to it, download it, add it to a collection or playlist.  Remove it when you’re done with it.

OPML Support allows you to import all of your podcasts from another podcast player, into PremoFM.  You can also export all of your PremoFM podcast subscriptions to an OPML file.  Access both features in settings.

iTunes Podcast Link opening allow you to click on an iTunes podcast link and open it in PremoFM, to that specific podcast.  Most podcasts list their iTunes link directly on their landing page.  This allows you to subscribe to podcast from the web on your Android phone more seamless.

Notifications have been improved.  Not only are they more readable, but now you’ll be able to check out show notes directly from your lock screen when you received a new episode notification.

Not familiar with PremoFM?  Check out the video below, then head to Google Play.

.

 

Cursors, RecyclerViews, and ItemAnimators

RecyclerView is an Android support library class that was introduced with the launch of Android “Lollipop” (compatible all the way back to Android 2.1).  It has a ton of new capabilities and built on a new architecture that promises flexibility and scalability for Android apps.

It’s ListView predecessor didn’t age too well and became cumbersome to use and extend.  ListView had a plethora of built in adapters (classes for binding data to the ListView), including an adapter for binding a ListView to a Cursor.  RecyclerView is great, but it’s missing a cursor adapter.  Because RecyclerView is so extensible, developers can easily write one, like the one written by Jason Yu:

Well, great, now I can use CursorLoaders, Cursors, and RecyclerViews in my Android app.  There’s one problem.  Backing up a bit, RecyclerViews introduced ItemAnimators.  They provide an easy way to run animations when items in the data backing a RecyclerView are inserted, removed, or changed.  If you’ve done any type of animations with ListView, you’d appreciate the ease of ItemAnimators.  RecyclerView determines which animation to run using RecyclerView.Adapter functions called in an implementing adapter:

  • notifyItemChanged
  • notifyItemRangedChanged
  • notifyItemInserted
  • notifyItemRangeInserted
  • notifyItemRemoved
  • notifyItemRangeRemoved
  • notifyItemMoved

Each of the mentioned functions requires an index, or range of indexes indicating which items were inserted, removed, or changed.

Going back to the problem, using a CursorLoader and Cursors in an Android app.  A dataset change (new record, deleted record, changed record) causes CursorLoader to initiate the allocation of a new Cursor that points to the new dataset.  This is great, we can pass that new Cursor to our CursorRecyclerViewAdapter seen above.  This is where our problem rears it’s head.  Our default implementation only knows that the dataset has changed, but doesn’t know the particulars (which records are new, removed, or changed)…so it calls notifyDataSetChanged, which causes the entire RecyclerView to be invalidated and redrawn.  It looks bad and is inefficient (see explanation at the end).

I needed a way to determine which records were inserted, which were removed, and which were changed.  I had a cursor to the old dataset and a cursor to the new dataset.  My only options were to diff them, building a list of records that were added, removed, and changed.  This allowed me to run the appropriate notifyItem* function and gain the corresponding animation that improved the user experience.  This is the RecyclerView adapter I use in my podcast app, PremoFM.

In this Gist, I’ve added several things to make this diff possible, built on the great work of Jason Yu.

First, my dataset contains a column I use to record a timestamp when that record is updated.  I pass the name of the column to the CursorRecyclerViewAdapter.  This becomes my comparison column (mComparisonColumn).  This saves me from having to compare each value, in each row in a record from one cursor, to each value in each row in a record in another cursor.

Second, I’ve added several functions that compare a new cursor and an old cursor:

  1. getChangeOrInsertRecords – iterates over the incoming cursor, then the outgoing cursor in an inner loop.  Matches each record on a row ID.  If a match is found, it then compares the comparison column.  If the values in that column don’t match, this record was edited.  If a record in the incoming cursor is not found in the outgoing cursor, then it’s a new record.  If the incoming cursor is empty, then all the records were deleted.  If the outgoing cursor is empty, then all the records in the incoming cursor can be assumed as inserted.
  2. getDeletedRecords – iterates over the outgoing cursor, then the incoming cursor in an inner loop.  It matches each record on a row ID.  If a record in the outgoing cursor is not found in the incoming cursor, then we can assume that the record has been deleted.

The result of running these two functions is a list that contains the index and that indexes difference (insert, remove, change).  I use this data in swapCursor to run the corresponding notifyItem* function and I gain animations when items change.

What does all of this get me?  Look at the user interface differences below.

notify_dataset  notify_item

Left: notifyDataSetChanged / Right: notifyItem*

Downsides?  The diff is a O(n^2) operation.  If you have a ton of records, this operation could take a while.  A possible optimization is to only diff the records that are visible in the RecyclerView and not the entire dataset behind each cursor.

Let me know if you have any questions by hitting me up on Twitter.  Also download my podcast app, PremoFM, in the Google Play Store now by going to premo.fm/app.

Why is using notifyDataSetChanged with RecyclerView bad and inefficient?  Calling notifyItemChanged causes RecyclerView to make a requestLayout for the RecyclerView.  So even if the text was updated in the first item in my RecyclerView, all the items will be redrawn.  The function notifyDataSetChanged triggers a call to RecyclerView.AdapterDataObservable.notifyChange.  In this function, each RecyclerViewDataObserver.onChanged function is called, which eventually triggers a requestLayout call.

Evidence is freely available for in the Android source code.