Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker
Aaron VonderHaar

Centering a View within a ScrollView

Aaron VonderHaar
Wednesday, March 14, 2012

We have a View that we want centered on the screen, but that must scroll when there isn’t enough room to show the view fully (for example, when the keyboard is up).

Our first attempt:

<ScrollView
    android:layout_width=...
    android:layout_width=...
    >
    <View
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        />
</ScrollView>

Unfortunately there is a bug in Android where setting layout_gravity=”center” on a ScrollView’s child causes incorrect scrolling when you start hiding and showing other views in the layout (the ScrollView will get stuck with an offset such that you can’t completely scroll to the top, and you can scroll past the bottom).

Here’s the solution:

<ScrollView
    android:layout_width=...
    android:layout_width=...
    android:fillViewport="true"
    >
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        />
        <View
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            />
        <View
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            />
        <View
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            />
    </LinearLayout>
</ScrollView>

The wrap_content in the LinearLayout is a bit misleading: fillViewport=”true” in the ScrollView will cause its child to always be at least as large as the viewport. (See also Romain Guy’s article about fillViewport.)

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Tyler Schultz

Android WebView loadData vs loadDataWithBaseURL

Tyler Schultz
Saturday, September 10, 2011

We spent some time trying to figure out why some html content would not load in our app’s WebView. We found trivial html can be loaded into a WebView using the loadData(String, String, String) method. Rendering complex pages with Javascript is a problem. It turns out the loadData() method requires the html to be URI escaped (RTFM? Bah!). There are additional characters that need to be escaped too, requiring some nasty boilerplate.

The simpler solution (workaround?) is to use loadDataWithBaseURL(String, String, String, String, String). Calls to this method do not require escaping. Pass along a garbage base url (or null), and an empty or null history url for success and profit.

webView.loadDataWithBaseURL("blarg://ignored", getData(), "text/html", "utf-8", "");
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Joe Moore

Introducing Android IntelliJ Starter and Android CI

Joe Moore
Tuesday, August 9, 2011

We have been doing quite a bit of Android development over the last year and a half at Pivotal Labs. Over time we have compiled a set of go-to tools, and libraries, and configuration settings that help make our development process as productive as possible. We are excited to publish two open source projects, each with the goal of helping new Android development projects hit the ground running: Android IntelliJ Starter and Android CI.

Android IntelliJ Starter

Android IntelliJ Starter (github project here) is a “template” IntelliJ 10.5 project created to bootstrap Android development in IntelliJ. Our goal: start test-driving your new Android project within minutes, not hours (or days) using the Robolectric framework for testing and Robojuice framework for dependency-injection. In addition to the starter application and unit tests many other supporting libraries are provided, including C2DM push notification libraries with a stubbed-out, documented C2DM implementation class.

Android IntelliJ Starter represents hard earned configuration knowledge as well: getting all these tools to work seamlessly in IntelliJ and on the command line using ant is no small feat. We’ve even provided instructions on how to remove the extra tools and libraries — configuration by deletion.

Android CI

Android CI (github project here) is intended to bootstrap Android continuous integration using Jenkins-CI (formerly Hudson). This project is a stripped-down version of Jenkins’ configuration directory, which is ~/.jenkins by default.

Android CI ships with one preconfigured job: running tests and building .apks for Android IntelliJ Starter. If your project starts as a clone or fork of Android IntelliJ Starter then Android CI’s configuration will work well for you with only a few simple changes.

Help Us Improve

At Pivotal Labs we are committed to making Android development as productive as possible. We will add more functionality to both projects over time and we encourage others to fork, enhance, send us pull requests, and to use the Issues tab on each Github project’s page to notify us of problems so we can fix them promptly.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ryan Richard

Embedding VideoViews in your UI: Caution!

Ryan Richard
Wednesday, August 3, 2011

Using a VideoView to play streaming videos is quite easy on Android, and the class provides handy callbacks to manage your UI too. For example:

    // set the Uri of the video
    videoView.setVideoURI(Uri.parse(videoUriString));
    // start streaming/playing the video
    videoView.start();

    videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) {
            // about to start playing
        }
    });

    videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) {
            // reached the end of the video
        }
    });

VideoViews can be embedded into your layouts and sized any way that you’d like. However, there is one serious limitation.

According to Romain Guy, SurfaceViews (such as VideoView, etc.) inside ScrollViews (or ListView, etc.) are not supported by Android.

For example, playing a video using a VideoView inside of a row of a ListView seems to work at first, until the user tries to scroll the list. As soon as the list starts to scroll, the video turns black. It keeps playing in the background but you can’t see it anymore because it renders the rest of the video as a black box.

As another example, when the VideoView is scrolled so that it is partially off-screen at the time that the video starts playing, the VideoView does not render the video in the correct location on screen, causing the video to appear mostly black.

So be careful where you decide to put your VideoViews. At first it seems that they can go anywhere, but they cannot.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Joe Moore

Fast Android Debugging with IntelliJ

Joe Moore
Friday, July 15, 2011

Sure, you can launch your Android app in IntelliJ’s debugger, but that’s slow. IntelliJ allows you to dynamically attach the debugger to a running device using the “Attach debugger to Android process” button. That’s fast!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Joe Moore

Android Tidbits 6/23/2011: Tabs and Colors

Joe Moore
Thursday, June 23, 2011

Pivotal Android Tabs

We have published a simple Android project that illustrates how to use tabs in an Android app: TabActivity, TabHost, TabWidget, and android:divider. Thank you Pivot Ryan, the original author, for taking the time to write and open source this. Check it out, fork it, and enjoy — https://github.com/pivotal/Pivotal-Android-Tabs

Tabs 1

Tabs 2

See Hex’d Colors

IntelliJ trick: in a colors.xml file, place your cursor on a hex value and hold down Shift. You’ll see a large preview of the color.

Hex colors in IntelliJ

Colors and States

(Repost from the 6/22/2011 Standup blog): You can use a selector drawable to set Android text color for the various states (focused, selected, etc.) using a drawable xml file. IntelliJ will complain and say this is invalid syntax but the application will use the file as you would expect. This only seems to work for the android:textColor attribute in TextViews.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Joe Moore

Android Tidbits 6/22/2011: Hiding Header Views

Joe Moore
Wednesday, June 22, 2011

Android ListView#addHeaderView and ListView#addFooterView methods are strange: you have to add the header and footer Views before you set the ListView‘s adapter so the ListView can take the headers and footers into consideration — you get an exception otherwise. Here we add a ProgressBar (spinner) as the headerView:

// spinner is a ProgressBar
listView.addHeaderView(spinner);

We’d like to be able to show and hide that spinner at will, but removing it outright is dangerous because we’d never be able to add it again without destroying the ListView — remember, we can’t addHeaderView after we’ve it’s adapter:

listView.removeHeaderView(spinner); //dangerous!

So let’s hide it! Turns out that’s hard, too. Just hiding the spinner view itself results in an empty, but still visible, header area.


Now try to hide the spinner:

spinner.setVisibility(View.GONE);

Result: header area still visible with an ugly space:



The solution is to put the progress bar in a LinearLayout that wraps it’s content, and hiding the content. That way the wrapping LinearLayout will collapse when its content is hidden, resulting in a headerView that is technically still present, but 0dip high:

  <LinearLayout
      xmlns:a="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content">
    <!-- simplified -->
      <ProgressBar
        android:id="@+id/spinner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
  </LinearLayout>

Then, set the layout as the header:

spinnerLayout = getLayoutInflater().inflate(R.layout.header_view_spinner, null);
listView.addHeaderView(spinnerLayout);

And when we need to hide it, hide the layout’s content, not the layout:

    spinnerLayout.findViewById(R.id.spinner).setVisibility(View.GONE);

Now the header disappears from view. No more ugly space at the top!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Joe Moore

Android Tidbits 6/21/2011: Unregister? Nah!

Joe Moore
Tuesday, June 21, 2011

C2DM Unregister Issues

It turns out when you follow the client-side C2DM unregistration process, this does not guarantee that those registration tokens are permanently unregistered for that device.

If we unregister as specified above and then send a push notification to that registration_id, the server receives an Error=NotRegistered as expected.

But, unexpectedly, when that device re-register with C2DM (and getting a new registration_id), the old registration_id is reactivated as well and can receive push notifications and does not result in a server-side Error=NotRegistered.

The end result: we implemented our server-side API to take both the new and old registration_ids when the Android client successfully registers with C2DM, allowing us to manually delete the old registration_id.

Drawable XML Files

Prefixing the name of a drawable xml file with “active_” seems to prevent android from using that drawable at all.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Tyler Schultz

Android Image Caching

Tyler Schultz
Saturday, June 18, 2011

Web development spoiled me. Set the src tag on an img tag and away you go. For the most part, browsers just do the right thing and cache these requests. Disappointingly, Android does not come with out of the box support for caching content downloaded from the network. Server apis often require SSL, request headers, support for different HTTP methods, multipart post bodies, etc. requiring the use of the apache http client libraries. These libraries are powerful, but are a bear to work with. As far as I can tell there is no support for caching http responses built into the apache libraries, requiring that you roll your own caching scheme. Images often do not have all these complicated HTTP requirements. They’re usually simple HTTP GET calls. This makes the java.net.* libraries appealing. The java.net libraries come with some handy classes that makes caching a breeze.

Here is a snippet that will cause your request to be pulled from the cache, should it exist. If it doesn’t exist, the retrieved response will be written to the cache.

URL url = new URL("https://pivotallabs.com/images/pivotallabs-logo.png");
URLConnection connection = url.openConnection();
connection.setUseCaches(true);
Drawable drawable = Drawable.createFromStream(connection.getInputStream(), "src");

In the past I tried to use the code above alone and found it didn’t work, and no solutions seemed obvious. The javadocs for URLConnection don’t mention anything about ResponseCache, but there is work to be done there to make it all work. The following example shows using Android’s cache directory to cache the responses. You’ll want to take a moment to read the Android docs about the cache dir. There are guidelines for use of the directory. This looks like a lot of code (Java, Java, Java, Java), but IntelliJ will allow you to cntl-shift-space your way to happiness and write most of it for you.

final String cacheDir = context.getCacheDir();
ResponseCache.setDefault(new ResponseCache() {
    @Override
    public CacheResponse get(URI uri, String s, Map<String, List<string>> headers) throws IOException {
        final File file = new File(cacheDir, escape(uri.getPath()));
        if (file.exists()) {
            return new CacheResponse() {
                @Override
                public Map<String, List<string>> getHeaders() throws IOException {
                    return null;
                }

                @Override
                public InputStream getBody() throws IOException {
                    return new FileInputStream(file);
                }
            };
        } else {
            return null;
        }
    }

    @Override
    public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
        final File file = new File(cacheDir, escape(urlConnection.getURL().getPath()));
        return new CacheRequest() {
            @Override
            public OutputStream getBody() throws IOException {
                return new FileOutputStream(file);
            }

            @Override
            public void abort() {
                file.delete();
            }
        };
    }

    private String escape(String url) {
       return url.replace("/", "-").replace(".", "-");
    }
});
</string></string>

This is a simple implementation that pays no attention to the response headers, returns no cached response headers, etc. but I think you get the gist. There is one gotcha: the URI passed into the put method does not contain the path info for the url, it must be retrieved from the URLConnection. Happy caching!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Joe Moore

Syncing IntelliJ Android .apk Files Using Dropbox

Joe Moore
Thursday, June 16, 2011

During a typical day of Android development we compile Android applications (.apk files) dozens of times, deploying to emulators and devices simply by pressing the Run button in IntelliJ. This is great for our in-office developers, but it’s more difficult for our remote-pairing developers to install those same .apks on their own emulators and phones. As a remote developer, I wanted seamless, instant access to all .apks we build during development. Using Dropbox and some IntelliJ configuration changes I now have all .apks we build available for me to install on my local emulators and phones just seconds after we build them on our development machines, 2500 miles away.

Dropbox

Many developers already know about Dropbox, the fast, super-secure file sharing service. I installed Dropbox on all of our machines and created Dropbox directories for each machine our team uses. They happened to be named after streets in San Francisco, Boulder, and Atlanta.

Dropbox dir

Symlinks

Next, on each machine, create an apk symlink to the appropriate Dropbox directory within IntellJ’s ide_bulid directory.

~/workspace/PivotalAndroid$ ln -s ~/Dropbox/PivotalAndroid/grafton_apks ide_build/production/PivotalAndroid/apk

Next, tell IntelliJ to output .apks to that directory. We have to be careful here, especially if you check your .iml file and .idea directory into source control. You can select Project Structure => Facets => (Your Android Module) => Compiler => APK Path:, but this will follow the symlink you created and change your project’s .iml file to include the machine-specific Dropbox directory, and thus a merge conflict on each machine.

Instead, you can manually edit the appropriate value in your .iml:

<!-- old value: -->
<option name="APK_PATH" value="" />

<!-- new value, where '/apk/' is the symlink to Dropbox: -->
<option name="APK_PATH" value="/ide_build/production/PivotalAndroid/apk/PivoalAndroid.apk" />

Note: IntelliJ will sometimes reset APK_PATH back to either the default or to the Dropbox dir. Watch for this and fix the path again when needed. We run into this once per week or so.

Result: Updates Galore!

Now I have .apk files streaming in from three different development machines; as a bonus, each development machine is synced with each other, so we all have access to all .apks. Whenever I want to install the latest .apk from another pair (or my own) onto my local test phone I simply pass the machine name into a script:

#!/usr/bin/env ruby
system "adb -d install -r ~/Dropbox/PivotalAndroid/#{ARGV[0]}-apks/PivotalAndroid.apk"

Thus, scripts/apk cedar installs the latest .apk file created on our machine named “cedar”.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Topics

  • agile (781)
  • rails (113)
  • testing (88)
  • ruby (83)
  • ruby on rails (70)
  • jobs (62)
  • javascript (55)
  • techtalk (44)
  • rspec (38)
  • ironblogger (32)
  • productivity (30)
  • activerecord (29)
  • gogaruco (29)
  • git (28)
  • nyc (27)
  • rubymine (26)
  • bloggerdome (23)
  • mobile (22)
  • process (21)
  • pivotal tracker (21)
  • cucumber (20)
  • design (19)
  • jasmine (19)
  • ios (18)
  • webos (17)
  • objective-c (17)
  • android (16)
  • tracker ecosystem (16)
  • palm (16)
  • "soft" ware (16)
  • fun (15)
  • ci (15)
  • cedar (15)
  • rails3 (14)
  • performance (14)
  • bdd (14)
  • gem (13)
  • css (13)
  • tdd (13)
  • selenium (12)
  • goruco (12)
  • bundler (12)
  • meetup (11)
  • railsconf (11)
  • nyc-standup (11)
  • capybara (10)
  • mac (10)
  • mojo (10)
  • chef (10)
  • api (10)
Subscribe to android Feed
  1. 1
  2. 2
  3. →
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >