Things I've Built While Building an Android Music Player
Five and a half years ago, I began building an Android application to stream music from my home server, which runs J River media center. Looking back, the journey has been as or more rewarding than the destination, as I only have about 20 active users on a given day beyond myself, but the software I've built along the way has been extremely fun to build. On the other hand, it's often been an act of frustration to build an application in Android's language of choice, as many of the tools I expected to have even back in 2012 were missing from the Java ecosystem. As a result, I built some of these tools myself.
Those tools were Lazy-J, a simple Lazy instantiation library, Artful, a library that serializes SQLite queries and results from and to Java classes, and Handoff, a promise library for easy asynchronous programming. While each of these libraries could probably have had many posts devoted to each of them, I find writing painful, so I'll devote this one blog-post to them all, and hopefully do them some of the justice they deserve.
Lazy-J
One of the things I missed when I was working in Java was the lack of lazy instantiation in the standard library. While there's all sorts of recommendations on how to do lazy instantiation, the approaches usually only apply to static methods, and rely on intimate knowledge of the runtime and Java spec to properly expect lazy operation. I needed a way to do a private final Lazy<MyObject> lazyObject = new Lazy<MyObject>()
, because I often needed to use a shared resource in my Android view objects without knowing when they'd be created.
I've seen IOC frameworks such as Dagger do this, but my application was never complicated enough to warrant using an IOC framework. I also confess to not being a huge fan of Java IOC frameworks due to their dependence on attributes (although it's understandable given the language's limitations).
Usage of Lazy-J is pretty straight-forward - a simple usage, just new-ing up an object, looks like this:
class MyClass {
private readonly Lazy<Object> lazyObject = new Lazy<Object>(Object::new);
}
I've abused Lazy-J excessively, for example, I don't want to have to run findView
all the time to get a view, so I have a class titled LazyViewFinder
, which will hold on to the reference to the view the first time you use it. This class lets me hold a reference to the view in my class, so I can reference my views like this:
public class Activity {
private LazyViewFinder<TextView> lblConnectionStatus = new LazyViewFinder<>(this, R.id.lblConnectionStatus);
protected void handleConnectionStatusChange(int status) {
.
.
.
final TextView lblConnectionStatusView = lblConnectionStatus.findView();
switch (status) {
case BuildingSessionConnectionStatus.GettingLibrary:
lblConnectionStatusView.setText(R.string.lbl_getting_library_details);
return;
}
.
.
.
}
}
You can find lazy-j at https://github.com/danrien/lazy-j.
Artful
Artful is a library which also began as a source of frustration: at the time (and even today?) there are not many easy, transferable ways to easily map the results of a SQL query on the built-in SQLite database to a Java object.
In C#, using the excellent Dapper library, one can just do this:
using (var connection = new SqlConnection())
{
return await conn.QueryAsync<MyObject>("select * from my_object");
}
Perfectly bridging the gap between SQL Queries and a strongly-typed languague, while still allowing each language to flex its respective strengths (small note: I think future interop efforts between languages should focus on these types of bridges).
Due to type erasure, we can't do quite the same thing in Java, but with Artful, I managed to get pretty close:
public Collection<Library> getLibraries(Context context) {
RepositoryAccessHelper repositoryAccessHelper = new RepositoryAccessHelper(context);
try {
return
repositoryAccessHelper
.mapSql("SELECT * FROM library")
.fetch(Library.class);
}
}
Artful is new'd up inside of RepositoryAccessHelper
, which does this:
public Artful mapSql(String sqlQuery) {
return new Artful(sqliteDb.getObject(), sqlQuery);
}
Since Artful caches the SQL queries and the reflection, it becomes pretty performant after the first round of serialization on a given class. It is by no means feature-complete, for example it can't serialize directly to primitive types - repositoryAccessHelper.mapSql("SELECT COUNT(*) FROM library").fetchFirst(Long.class)
would likely give you nothing - but it has served me remarkably well, without memory leaks as well.
You can find Artful at https://github.com/namehillsoftware/artful.
Handoff
Asynchronous programming in Java is really painful. Java has Future
, which purports to be the way to perform asynchronous behavior, but it merely introduces a spin-lock to achieve receiving a value synchronously on your calling thread. While this may be fine behavior for a server application (although many a NodeJS and nginx server may disagree with you on the principle of thread starvation), for a desktop application, knowingly introducing blocking behavior into your application is border-line offensive.
Android attempted to make this better with AsyncTask
, but I found it to be nearly as painful to work with as Future
:
- It's difficult to easily chain one asynchronous action to another without developing verbose APIs
- It has messy internal state that leaks into your consumption of the library with unchecked exceptions
- Results post back to the UI thread that it was called on, which is often all that is wanted in simple applications, but the second you have to perform additional asynchronous work, starts to cause troubles
Handoff is the last library I've developed for my little music player, and the one of which I am proudest. Handoff aims to be a Promises A+ like promise library for Java. While Java has CompletableFuture
, I find its API surface layer to be rather large. In this instance, I also found C#'s Task library to be very verbose - they had to introduce an entirely new language primitive just to make it easier to work with (async/await
)! I even speculate that the Task
library was explicitly written to set the stage for async/await
;).
I really like the ergonomics of Javascript's Promise class, and thought it would be fun to see if I could make something like that for Java. It has both been fun to develop and tremendously beneficial! I wish it was easy to show a side-by-side comparison of my app's responsiveness before and after using Handoff
, but the difference has been night and day for me - ever since switching to a promise-like async model, I rarely get UI hangs or unresposive warnings from the OS, and unexpected IllegalStateException
hangs have gone completely away.
Handoff in the simple case is used like this:
new Promise<>(messenger -> {
final IPositionedFileQueueProvider queueProvider = positionedFileQueueProviders.get(nowPlaying.isRepeating);
try {
final PreparedPlayableFileQueue preparedPlaybackQueue = preparedPlaybackQueueResourceManagement.initializePreparedPlaybackQueue(queueProvider.provideQueue(playlist, playlistPosition));
startPlayback(preparedPlaybackQueue, filePosition)
.firstElement() // Easily move from one asynchronous library (RxJava) to Handoff
.subscribe(
playbackFile -> messenger.sendResolution(playbackFile.asPositionedFile()), // Resolve
messenger::sendRejection); // Reject
} catch (Exception e) {
messenger.sendRejection(e);
}
});
The returned promise can then be chained as you'd expect:
playlist.promiseFirstFile()
.then(f -> { // Perform another action immediately with the result - this continues on the same thread the result was returned on
// perform action
return f; // return a new type if wanted, return null to represent Void
})
.eventually(f -> { // Handoff the result to a method that is expected to produce a new promise
return new Promise<>(m -> {
});
})
.excuse(e -> { // Do something with an error, errors fall through from the top, like with try/catch
return e;
})
Handoff can be found here - https://github.com/danrien/handoff.
And the application that motivated me to build all of these little libraries is Music Canoe, which can be found here - https://github.com/danrien/projectBlue.
Note posted on Sunday, April 8, 2018 12:02 PM CDT - link