Wednesday, 21 November 2012

Pinterest styled list for Android

Pinterest is more and more popular and many apps use its "masonry" pattern. I used the same pattern in my last project and must say it wasn't as easy job as it looked at first. Here's what I'd been doing...

At first logical solution was to use vertical LinearLayouts inside a ScrollView - easy and simple.
All I had to add is code which adds elements proportionately in each LinearLayout.
<ScrollView>
<LinearLayout
   orientation="horizontal">

   <LinearLayout
     android:layout_weight="0.5"
     orientation="vertical">

   <LinearLayout
     android:layout_weight="0.5"
     orientation="vertical">

</LinearLayout>
</ScrollView>
This worked fine at the beginning but when I added more than 100 items, as expected, app started to crash. Well if you think about it it was obvious, with this approach we don't use recycling so layout constantly had 100 items filled with layouts, pictures, formatted text..

Second solution I thought of was two synchronized ListViews. They have internal caching so all I had to do is synchronize them.
<LinearLayout 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    android:paddingLeft="10dp"
    android:paddingRight="10dp">

    <ListView
        android:id="@+id/list_view_left"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:paddingRight="5dp"
        android:scrollbars="none" >
    </ListView>

    <ListView
        android:id="@+id/list_view_right"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:paddingLeft="5dp"
        android:scrollbars="none" >
    </ListView>

</LinearLayout> 
My first attempt was to add OnTouchListener which will pass the touch event to the opposite list and OnScrollListener which will update first opposite child.
listOne.setOnTouchListener(new OnTouchListener() {
    @Override
       public boolean onTouch(View arg0, MotionEvent arg1) {
       listTwo.dispatchTouchEvent(arg1);
       return false;
    }
});
listOne.setOnScrollListener(new OnScrollListener() {
   @Override
   public void onScrollStateChanged(AbsListView arg0, int arg1) {
   }
   @Override
   public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3) {
          if (l1.getChildAt(0) != null) {
              Rect r = new Rect();
              l1.getChildVisibleRect(l1.getChildAt(0), r, null);
              l2.setSelectionFromTop(l1.getFirstVisiblePosition(), r.top);
          }
    }
});  
Well this seemed as good solution. It was working with single list, scrolling was smooth so adding the same code to the opposite list should work fine but unfortunately it didn't. They ware both synced on first visible child so when one would disappear the other would automatically be positioned to match the new one. This made scrolling feel quite unnatural so other solution had to be found.

It occurred to me that I can just calculate distances from top of both elements, subtract one from another and add offset of the current element. Hmmm, well let's be more precise it took me a while get to this calculation but if you draw an example yourself, you'll see its just logical.
Source can be found on Github  
This solution works fine. I have few bugs to fix, simplify the code - make it more simple to use and that should be it. 
Still it bothers me if this can be implemented by using BaseAdapter and AdapterView. I'll  have an option to use adapters caching and what remains is child positioning. Well, I'll give it a try as soon as I find spare time...

Friday, 6 July 2012

List of Android Adapters

Month ago I first saw ResourceCursorAdapter being used in Android development.
That gave me enough curiosity to summarize them all in a single place.


There are several types which differ by data source and manipulation complexity available to the user. Lets start...

BaseAdapter
Simple adapter implementation used for ListView. You provide data by overriding required methods that provide elements for the list. Mostly used for simple list implementations.

SimpleAdapter 
Very useful adapter that allows us to map data to elements in the layout file. You provide a grid (two same sized arrays) which tells which element goes into which place and map that contains all the data needed for this grid to know what to put where.

ArrayAdapter
For me less convenient than the SimpleAdapter since default behavior is to map String array elements to single text field. You can override it and provide an array of complex object which have data to populate more complex rows.

SimpleCursorAdapter
An easy adapter to map columns from a cursor to elements defined  in the layout file. You can specify which columns you want, which views you want to display the columns, and the layout file that defines the appearance of these views.


CursorAdapter
More sofisitcated implementation of the SimpleCursorAdapter which is abstract so its up to you
to create views with cursor providing you the data from database.

ResourceCursorAdapter
Very similar to CursorAdapter. Doesn't have a newView method which makes it more appropriate
for not so complex views. 

SpinnerAdapter
Haven't used this one that much but from what I've seen enables you to define two views, one with data and the second dropdown with choices.

SimpleCursorTreeAdapter
The following two are my most favorite. They allow us to define list elements together with the groups they belong in. Very convenient for situations where you need list with grouped data. You provide both child and group views with appropriate data binding

CursorTreeAdapter
Same as previous but with more liberty and requirements. Better for complex lists.

HeaderViewListAdapter
ListAdapter used when a ListView has header views. This ListAdapter wraps another one and also keeps track of the header views and their associated data objects.

WrapperListAdapter
Haven't seen this one before. Haven't seen any examples but it seems it wraps another list adapter. Strange...

Well that's all I thing....
Now I have them all in single place so I don't have to look around. Sweet :)


Friday, 29 June 2012

Installing Jelly Bean on Galaxy Nexus

As you know Google unveiled Android 4.1 Jelly Bean which looks amazing. Official ROM was leaked to the public and is now available for download. If you have a rooted Galaxy Nexus and if you want to play around with the latest Android (on your own responsibility) follow the instructions bellow. 



First of all, you need a ClockworkMod recovery that you can install the usual way, but also through the ROM manager. The procedure will delete personal information, but not the contents of internal memory.

  1. Download Jelly Bean ROM
  2. Move it to the phone's internal memory.
  3. Turn off your phone.
  4. Start the phone in recovery mode.
  5. Once the menu appears, enter your organization and recovery "install zip from SD card"
  6. Select Wipe & factory reset.
  7. Flash the ROM.
  8. Select Reboot System.

And there you have it ... Jelly Bean on Galaxy Nexus

Friday, 18 May 2012

Using RVM with different versions of Rails on Mac OSX

I had to use both jRuby and Ruby with different versions of Rails on the same machine which turned out to be quite a frustration. I've heard about RVM which separates Ruby environments together with their gems from one another. I decided to give it a shot...
RVM installation (stable version)
  1. first lets install the RVM
    curl -L get.rvm.io | bash -s stable 
  2. after installation we'll have to reload the console and run:
    source ~/.rvm/scripts/rvm
  3. test if everything was OK
    type rvm | head -n 1 
    to get the message rvm is a function
Ruby installation 

I will be using jRuby 1.6.5 and Ruby 1.9.3 so we need to install those two:
rvm install  1.6.5
rvm install  1.9.3 
After installations finish, run rvm list to get the list of installed rubies.
I will use jRuby and set it as default as I use it most of the time:
rvm use 1.6.5 --default

Rails installation

First we'll need Gemsets. They can be regarded as folders with separate ruby setups, gems and irb, all separate and self-contained - from the system, and from each other.
Lets create one for the Rails we require 2.3.8
rvm gemset create rails238
rvm use 1.6.5@rails238

To install rails and remaining gems we can use gem install but since I use geminstaller we can just run this gem and it does the job for us.
gem install geminstaller
geminstaller

Before we can start anything we have to set path for our new jRuby in .profile and refresh it
with source ~/.profile.
At the end we can override rvmrc to load this gemset automatically:
rvm --rvmrc --create 1.6.5@rails238

For Rails 3.2, which I will use with Ruby,  the gemset stuff is same as before except I'll use bundle install.

We'll these are my notes for RVM installation. Off to checkout Pry, heard it's way better than IRB....

Tuesday, 17 April 2012

Android: Parsing JSON with Generics

Parsing JSON in Android is quite a common task but as project grows this can become quite messy if not done properly at the beginning.
I will create a simple function which parses JSON by passing a expected class type and getting the instance of the same class if request is successful.
We will achieve this by using Java Generics. From what I've seen so far, many people run away from this feature but in this case they are just the right tool for the job.
So here's how the stuff should look like:
/**
 * Converts recieved JSON into passed type
 * @param url - address to which are we posting
 * @param requestObject - post data
 * @param responseType - type of the response we are expecting
 * @return - instance of the class
 * @throws Exception - use more precise exceptions
 */
public < T extends Response > T simplePostExample(String url, 
  Object requestObject, Class responseType) throws Exception {
                
  // standard post request with json content
  HttpPost request = new HttpPost(url);
  request.setHeader("Accept", "application/json");
  request.setHeader("Content-type", "application/json");
                
  // converting post data to json
  String json = new Gson().toJson(requestObject);
  StringEntity requestEntity = new StringEntity(json);
  request.setEntity(requestEntity);
                
  // executing request
  HttpResponse httpResponse = httpClient.execute(request);
  HttpEntity entity = httpResponse.getEntity();

  checkResponse(httpResponse, entity);

  if (entity != null) {
   InputStream content = entity.getContent();
   Reader reader = new InputStreamReader(content);
                        
   // convert response to new instance of the expected class
   T object = new Gson().fromJson(reader, responseType);
   return object;
  } else {
   throw new Exception("Something went wrong…");
  }
 }
        
 // check if everything is as it should be
 private void checkResponse(HttpResponse httpResponse, HttpEntity entity)
   throws Exception {
  int statusCode = httpResponse.getStatusLine().getStatusCode();
  if (statusCode != HttpStatus.SC_OK)
   throw new Exception("Something went wrong…");

  Header contentType = entity.getContentType();
  if (contentType == null
    || !contentType.getValue().startsWith("application/json"))
   throw new Exception("Something went wrong…");
 }

All responses inherit Response class which can also be useful for storing data that all responses have ( status, message...). This is just a quick example, but can later be extended to use more features like other http requests, credentials and etc...

Monday, 12 March 2012

Android layout for grouping views in iOS style

Although this is default for iOS (UITableView) its often used in Android also,
so I decided to write a simple library for this purpose. This is the first edition with only plain, edit text and few basic options so everyone is free to participate!
GroupedTextView on Github

Friday, 3 February 2012

Installing subclipse on Mac OSX Lion

I've just installed Subclipse SVN plugin version 1.8.x. but as always there ware problems. This time it was JavaHL library. Default instalation from openCollabNet is 1.6.7 which isn't compatible with this version of Subclipse:

Subclipse Version SVN/JavaHL Version
1.8.x 1.7.x
1.6.x 1.6.x
1.4.x 1.5.x

One option would be to revert to previous version which I didn't quite like.
Second is "unrecommended" installation from MacPorts.
Instalation goes ok but with few tweeks! Just run the following command(s) and you are set to go!

sudo port install subversion-javahlbindings

If you have problems with updating from previous version:
sudo port clean subversion-javahlbindings
sudo port selfupdate
sudo port upgrade outdated


If you have problems with updating db64:
sudo port clean -f --all db46
sudo port selfupdate
sudo port install db46