Showing posts with label coding tips. Show all posts
Showing posts with label coding tips. Show all posts

Wednesday, December 21, 2011

'Tis the season for sharing (via your Android code)


Come Facebook, come Twitter, come Tumblr and Flickr!

No app is an island, we often want to be able to share the cool things we create with our apps with the world, whether it's the latest social network to on-sell our personal details, or just plain old email to your gran.



.. And here's how.

Lets say we have an image file located here:
String fileLocation = "/mnt/sdcard/SomeMediaFiles/aMediaFile10.jpg"

We can share this easily by passing the reference to our file location into the below method:
 
    public void shareFile(String newFilePath) {
      {Intent share = new Intent(Intent.ACTION_SEND);
      share.setType("image/jpeg"); 
 
//declare the MIME type of the file. .. What you pass into share.setType determines what apps appears in your Sharing list.

      Uri imageUri = Uri.fromFile(new File(newImagePath));
      share.putExtra(Intent.EXTRA_STREAM, imageUri);
   
      startActivity(Intent.createChooser(share, "Share"));
 }
}
And this is basically what is the result (actual apps listed will depend on what is installed on your users device):


... Easy.


Saturday, December 10, 2011

OutOfMemory errors when populating a Gallery


I've just spent the last couple of hours wrestling with OutOfMemory errors when populating a Gallery widget with photos, and as fun as it's been, I think I've sorted it out and thought I'd share my solution with you.

For starters, the reason we're getting OutOfMemory errors in the first place is that the heap size on Android devices is limited to something like 16 MB on a G1 and 24 MB on a Nexus one.

As soon as you start to work with media files you quickly start to learn that you need to work to manage your memory usage with Android as a result of this limitation.

I would also like to thank direct to device debugging, I couldn't have done this without utilizing it, I would go as far as saying that it is essential in situations like this.

Here is the full code of one of my imageAdapters,  I'm using this to provide the images to my Gallery widget like this:

galleryTop.setAdapter(new ImageAdapterTop(this));


The Adapter class :

public class ImageAdapterTop extends BaseAdapter {
   int mGalleryItemBackground;
   private Context mContext;


    File[] allTopSlices = Utils.getAllTopSlices();  
    String[] fullPathAllTopSlices = new String[allTopSlices.length];
    Uri[] uriAllTopSlices = new Uri[fullPathAllTopSlices.length];


   public ImageAdapterTop(Context c) {
       mContext = c;
       TypedArray a = obtainStyledAttributes(R.styleable.HelloGallery);
       mGalleryItemBackground = a.getResourceId(
               R.styleable.HelloGallery_android_galleryItemBackground, 0);
       a.recycle();
   }


   public int getCount() {
       return uriAllTopSlices.length;
   }


   public Object getItem(int position) {
       return position;
   }


   public long getItemId(int position) {
       return position;
   }


   public View getView(int position, View convertView, ViewGroup parent) {
    int screenHeightPx = Utils.GetScreenHeight(getWindowManager().getDefaultDisplay());
   
    for(int i = 0; i < allTopSlices.length; i++){
    fullPathAllTopSlices[i] = allTopSlices[i].getAbsolutePath();
    }
   
    for(int j=0; j < fullPathAllTopSlices.length; j++){
    uriAllTopSlices[j] = Uri.parse(fullPathAllTopSlices[j]);
    }
   
       ImageView i = new ImageView(mContext);
     
       recycleDrawable(i);


       i.setImageBitmap(Utils.readBitmap(uriAllTopSlices[position].toString()));
     
       i.setLayoutParams(new Gallery.LayoutParams(400, (screenHeightPx-50) /3));
       i.setScaleType(ImageView.ScaleType.FIT_XY);      
       i.setBackgroundResource(mGalleryItemBackground);


       System.gc();
       return i;
     
   }
}



and the referenced recycleDrawable :

private void recycleDrawable(ImageView i) {
BitmapDrawable currentBitmapDrawable = (BitmapDrawable)i.getDrawable();

if(currentBitmapDrawable != null){
currentBitmapDrawable.getBitmap().recycle();        
}
        System.gc();
}



and readBitmap methods:

public static Bitmap readBitmap(String selectedImage) {
Bitmap bm = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;


try {
bm = BitmapFactory.decodeFile(selectedImage, options);
}
catch (OutOfMemoryError e) {
e.printStackTrace();
}

return bm;
}

Probably worth mentioning that the 'options.inSampleSize' reference, which is downsizing the image is pretty much essential when working with large files with the memory constraints associated with current Android devices.

Hope that helps someone out there.


Sunday, November 20, 2011

'An error has occurred' when working with Bitmaps



On a recent project I needed to rotate and slice images taken from the camera, and I found that I was getting regular, but annoying to track down 'an error has occurred' messages.

I couldn't see anything in particular that was wrong with my code, and what made it worse was that I wasn't receiving any errors at all in the emulator, only when I deployed the app to my phone for testing.

It took a few hours to figure out what was going on, so I thought I'd share it here and maybe save somebody else some time.

Like a lot of errors, it seemed painfully obvious after the fact, but the cause was I was basically running out of memory when creating the bitmap objects below. From my experience, you can only create one or two bitmap objects before you will encounter issues like this, because the heap size on Android devices is limited to something like 16 MB on a G1 and 24 MB on a Nexus one.
.. That's not a lot of 6 MB photos.

Luckily, it's super easy to fix.
All you need to do is recycle the bitmaps once you've finished with them, as per below code, and as pointed out by Romain Guy here,

"..The problem is that it can take a couple of GC cycles for a Bitmap to be properly released on Android before Android 3.x. Even if you call recycle() I believe the bitmap counts against your heap usage until at least the next GC. This is one of the very few situations where I would advise you to force a GC by calling System.gc()."

Matrix mat = new Matrix();
mat.postRotate(90);

Bitmap photoRotated = Bitmap.createBitmap(bitmapToSave, 0, 0, bitmapToSave.getWidth(), bitmapToSave.getHeight(), mat, false);

Bitmap photoCut1 = Bitmap.createBitmap(photoRotated, 0, 0, photoRotated.getWidth(), heightOfSlice);

photoRotated.recycle();

//save photoCut1 bitmap here

photoCut1.recycle();
System.gc();


So don't forget to recycle and call System.gc() a couple of times when working with large files like images and hopefully, like me, your errors messages will disappear.

Monday, June 13, 2011

How to get the size and orientation of your device screen



Found this great snippet over at http://www.androidsnippets.com/ that allows you to find the size and orientation of the device screen at runtime, and thought I'd share it:

/* First, get the Display from the WindowManager */
Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();

/* Now we can retrieve all display-related infos */
int width = display.getWidth();
int height = display.getHeight();
int orientation = display.getOrientation();
// see http://androidsnippets.com/get-size-and-orientation-of-the-screen

Wednesday, March 23, 2011

Creating a simple border with rounded corners for a layout or view

not the border in question..


Hi all, just thought I'd post this simple snippet that I've just found.
This allows you to simply add a border ( even with rounded corners!) to a layout or view.

All you need to do is create a new xml file somewhere in /res/drawable called 'the_border.xml'
with this or similar as the contents:

<shape xmlns:android="http://schemas.android.com/apk/res/android"> 
    <stroke android:width="1dp" android:color="#FFFFFF" /> 
    <padding android:left="7dp" android:top="13dp" 
            android:right="7dp" android:bottom="17dp" /> 
    <corners android:radius="4dp" /> 
</shape> 

You can then reference this as the background of an item in your layout like this (in xml)

android:background="@drawable/the_border"

or like this in code:

llErrorMessage.setBackgroundResource(R.drawable.the_border);

Nice eh?

Sunday, February 13, 2011

TextView background is black when setting colour from xml?


I had a colour defined in a 'colours.xml' file in my project's 'values' folder like so:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="tvBackground">#337700</color>
</resources>

Which, for those of you who cannot transpose directly from hex, looks like this:

My code looked like this:

   TextView tv = new TextView(getApplicationContext());
       
   tv.setBackgroundColor(R.color.tvBackground);

Seems reasonable doesn't it?

But when I ran my code, my shiny new little TextView came out with the background colour of this:


(that's #000000 in hex, or 'black' btw)

What was going on here?
Turns out, I wasn't using the correct method to set the background colour so that it comes out correctly, what I should have used was either of these:

   tv.setBackgroundResource(R.color.tvBackground);
- or -
   tv.setBackgroundColor(getResources().getColor(R.color.tvBackground));


.. Not especially well documented, but easily fixed.


Anyhow, now my TextView shows with the background colour of:


.. So all is now well with the world. Thought I'd just post this in case it helps someone out there (or myself when I forget next time)


until that next time, bye bye.


Sunday, January 23, 2011

How to get images dynamically


Lets say you're storing items in a slqite db for your new, soon-to-be-award-winning application, and one of the columns you're storing is the name of the image to associate with that item.

How on earth do you reference it from within your app so you can display it?
Good question! Here's how:


//first get a reference to the ImageView in our XML layout file we want to display the image in..

ImageView imgVw = (ImageView) customView.findViewById(R.id.imgVwInXML);

//Next check that there is actually an image file name in the db column:

if (OurDBCursor.getString(OurDBCursor.getColumnIndex("imageName")) != null) 
{
 
//if there is a name for the image, get that name..

  myImageName  = OurDBCursor.getString(OurDBCursor.getColumnIndex("imageName"));  
 

//get a reference to the image (located in our drawable folder in our project):
          
  int resID = getResources().getIdentifier("com.BlueMongo.Test:drawable/" + myImageName, null, null);  
 

//assign the resouce with that ID to our ImageView:
               
  imgVw.setImageResource(resID);
}

.. And that's it.

Hope that helps. Have a great day.

Sunday, November 21, 2010

Finding the users language settings via code



We've seen before how easy it is to reference difference resources based on the users language settings, but that was more-or-less managed by the Android framework itself.

What if you want to programmatically make decisions in your code based on what the user's language and local settings are?

Turns out, it's very easy, and here's how:
(this example assumes you have a TextView in your layout.main called tvLocale)

package com.Bluemongo.LanguageTest;

import java.util.Locale;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class LanguageTest extends Activity 
{
 @Override
 public void onCreate(Bundle savedInstanceState) 
 {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
        
  TextView tvLocale = (TextView) findViewById(R.id.tvLocale);
        
       if (Locale.getDefault().getLanguage().equals("en")) 
        {tvLocale.setText("you're speaking English");}

        if (Locale.getDefault().getLanguage().equals("de")) 
        {tvLocale.setText("you're speaking German");}
        
        if (Locale.getDefault().getLanguage().equals("fr")) 
        { tvLocale.setText("you're speaking French");}   
        
  }
}


.. Told you it was easy.

Thursday, October 14, 2010

Spinner down arrow stretching?

.. Styling the inner and outer views of a Spinner individually.


I was working with a Spinner with a lot of rows fetched from a database, and wanted to put a bit of padding around each item to make it easier for the user to select them.

So I went into my res\layout\spinner_view_row.xml (which controls how the Spinner rows display) and added this:

android:padding="5dip"

Then, when I went and re-ran my app, what used to look like this:



.. now looks like this:



.. Ooops. 


Looks like a condom doesn't it. Not what I was trying to achieve, really. If I made the padding large enough, it will also look like that before I've even selected anything.

Not what I wanted at all.

But I do want that padding around each item in my Spinner, otherwise my users will have too much trouble choosing items from my latest super-dooper, take-the-world-by-storm, #1 in the world market app, and it might not stay #1 for long at all.

Luckily, the answer is really, really simple.

You might recogise the below as the piece of code that binds a cursor from the database (containing all the items we want to display), to the xml view spinner_view_row, located at  res\layout\spinner_view_row.xml (this is the xml file in which we put the extra padding, above).


  final SimpleCursorAdapter ingredientAdapter =
  new SimpleCursorAdapter(this, android.R.layout.simple_spinner_item, ingredientsCursor, from, to); 
  ingredientAdapter.setDropDownViewResource(R.layout.spinner_view_row);

  spnIngredients.setAdapter(ingredientAdapter);

All we need to do to avoid the stretchy condom spinner arrow is to define another xml view in res\layout\ and call it something like spinner_view_closed, then paste into it the same code that you have in spinner_view_row.
Simply then customise this xml to have less padding, or a smaller text size for instance, then replace the reference to
simple_spinner_item
with a reference to this new xml file, like this:

  final SimpleCursorAdapter ingredientAdapter =
  new SimpleCursorAdapter(this, R.layout.spinner_view_row_closed, ingredientsCursor, from, to); 
  ingredientAdapter.setDropDownViewResource(R.layout.spinner_view_row);

  spnIngredients.setAdapter(ingredientAdapter);


.. and your new Spinner will look like this when open:



.. and this when closed.



Easy eh?
.. Told you so.

Monday, October 4, 2010

Using EditText's inputType to control what type of keyboard is shown

As software developers, there are many circumstances in which you'll want to limit the input options available to your users. We've all seen situations along the lines of the person who enters 'two' in a field where we've only been expecting the number 2. Oh how that screws things up! Oh the laughs we've had eh?

Fortunately, in Android it's easy to gently shepherd our precious users to input the sort of data we're expecting, and we do that with the editText's inputType attribute.

You can set your editText inputType as ‘Phone’ for example, and the user can able to type only numbers. If it is ‘Time’ it will allow only time related characters to be entered. Handy eh?

There are many options, and I've included (what I think is) all of them at the end of this post for your pleasure.

In the meantime, here are some examples that will hopefully illustrate this option:

        <EditText android:id="@+id/etWidth1"
            android:hint="@string/widthLabel"
            android:minWidth="75dip"
            android:inputType="textCapWords"
            android:lines="1"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content">
        </EditText>

notice the capitalised first letter of the each sentence, that happens automatically!



        <EditText android:id="@+id/etWidth1"
            android:hint="@string/widthLabel"
            android:minWidth="75dip"
            android:inputType="textCapCharacters"
            android:lines="1"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content">
        </EditText>




        <EditText android:id="@+id/etWidth1"
            android:hint="@string/widthLabel"
            android:minWidth="75dip"
            android:inputType="text"
            android:lines="1"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content">
        </EditText>
ok, so nothing special to see here..


        <EditText android:id="@+id/etWidth1"
            android:hint="@string/widthLabel"
            android:minWidth="75dip"
            android:inputType="phone"
            android:lines="1"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content">
        </EditText>



        <EditText android:id="@+id/etWidth1"
            android:hint="@string/widthLabel"
            android:minWidth="75dip"
            android:inputType="textUri"
            android:lines="1"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content">
        </EditText>


Remember, limiting the allowable input characters is only one part of good design.

You still need to run a sanity check on all fields that allow the user to enter data.

This is absolutely essential in many, many situations. Do a google search for 'sql injection' for more information on how unsafe fields can result in your entire system being vulnerable.

Here is (what I think is) the full list of EditText InputTypes available to you :

text
textCapCharacters
textCapWords
textCapSentences
textAutoCorrect
textAutoComplete
textMultiLine
textImeMultiLine
textNoSuggestions
textUri
textEmailAddress
textEmailSubject
textShortMessage
textLongMessage
textPersonName
textPostalAddress
textPassword
textVisiblePassword
textWebEditText
textFilter
textPhonetic
number
numberSigned
numberDecimal
phone
datetime
date
time

Sunday, August 1, 2010

Using spinner.setSelection & finding the spinner doesn't show the selected item when closed?



Ok, I've just spent a couple of hours trying to figure this out, and now I have, I thought I'd share the incredibly simple solution with you.

The issue: I was needing to set a Spinner's selected item via code, but found when calling the Spinner's setSelection method and passing in the position to set it to, something odd would happen, the closed spinner would appear blank, yet, when clicking on it, the item I've asked to be selected would be correctly located at the top of the spinner.

It looks like a Spinner is not told to redraw when using .setSelection(position), what you have to do is call .setSelection(int position, boolean animate) unless you want your selection to happen silently behind the scenes.

Odd, but easily sorted out.

The incredibly simple solution:

This won't show the fact that the Spinner selection has been set:


spnIngredients.setAdapter(ingredientAdapter);
spnIngredients.setSelection(position);

This will:


spnIngredients.setAdapter(ingredientAdapter);
spnIngredients.setSelection(pos, true);




Hope that helps someone out there.
.. Happy Spinning.

Tuesday, June 29, 2010

How to tile a background image in Android


For one of the apps I'm working on I wanted to have a nice pixel pattern tiled behind my widgets.
After a little bit of hunting around I found this tutorial, and I thought I'd clean up the lessons within and show you how.

Here's the contents of my main.xml layout file,

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/backrepeat"
    android:gravity="center_horizontal"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />  
</LinearLayout>



which is referenced in code in the standard way like this:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
...// (rest of onCreate method continues here..)

Now note this line:

android:background="@drawable/backrepeat"


What's going on there?
.. Glad you asked!

Here's a quick screenshot of the contents of one of my drawable folders in my project:


What is this
backrepeat.xml?

Well, here's the contents of that file here:

<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/scale1"
android:tileMode="repeat"
android:dither="true" />



Can you see what's going on?
Backrepeat.xml defines an instance of the BitmapDrawable class, and that class references our simple scale1.jpg, located in the drawable-hdpi folder.

Simply by adding the:

<bitmap
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:src="@drawable/scale1"
    android:tileMode="repeat"
    android:dither="true" />

line in bold, we are able to achieve results such as this:



Easy isn't it?

One thing to keep in mind is that you should have folders drawable-hdpi, drawable-mdpi & drawable-ldpi, you'll need to add this backrepeat.xml file and the relevant images to each of these to allow this functionality in high, medium and low dpi (dots per inch) screen sizes.

Enjoy.

Tuesday, March 16, 2010

Default text for an EditText? Here's a Hint



Sometimes it can be a nice idea to put default values in an EditText to let your users know what sort of information you're wanting them to enter.

What I had been doing previously was setting the .text value of the EditText to some default value, then clearing it onClick, after checking if the value was still the default, like this:

etName.setOnClickListener(new OnClickListener() {


String name = etName.getText().toString();
String origVal = getResources().getText(R.string.NameDefault).toString();

@Override
public void onClick(View v) {
if(name.equals(origVal));
{
etName.setText("");

}

}
});


Here's my String resource, stored in res/strings.xml as used above:

<?xml version="1.0" encoding="utf-8"?>
<resources>

<string name="NameDefault">Enter your name</string>
</resources>


But there is another, easier way, and it's called a Hint.

Simply by adding the below, bold attribute into my xml layout, my EditText by default contains a default string, which only exists when the EditText is empty.

It's all done for me!

.. here is my layout xml, note the android:hint line:

<EditText android:id="@+id/etName"
android:hint="@string/NameDefault"
android:minWidth="100dip"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
</EditText>



Saturday, March 6, 2010

Handling Button clicks in a ListView Row


Lets say you have a spinner widget, like the one we created in our 'colours' example, and you wanted to put a button on each row of your spinner for your users to click (everyone loves clicking buttons, right?).

You might have a layout a bit like this simple one below:

<LinearLayout android:id="@+id/LinearLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">

 <TextView android:text="this is a row"
     android:id="@+id/tvViewRow"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
 </TextView>
 <Button android:text="Click me!"
     android:id="@+id/BtnToClick"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:onClick="myClickHandler">
 </Button>

</LinearLayout>

.. Containing just a textView and a Button View.

A listView with textual, non-clickable views in it responds to Click events via the OnItemClickListener event.

But once you put a button in the ListView, this event no longer fires.

So how do you capture the button's click event, and find what row in your ListView a clicked button is located in?

You may have noticed this android:onClick="myClickHandler"> in our layout above..
What this does is tie every click of every instance of that button, in every row of our ListView to one single handler, located in the code of our ListActivity class below:

    public void myClickHandler(View v) 
    {
          
        //reset all the listView items background colours 
        //before we set the clicked one..

        ListView lvItems = getListView();
        for (int i=0; i < lvItems.getChildCount(); i++) 
        {
            lvItems.getChildAt(i).setBackgroundColor(Color.BLUE);        
        }
        
        
        //get the row the clicked button is in
        LinearLayout vwParentRow = (LinearLayout)v.getParent();
         
        TextView child = (TextView)vwParentRow.getChildAt(0);
        Button btnChild = (Button)vwParentRow.getChildAt(1);
        btnChild.setText(child.getText());
        btnChild.setText("I've been clicked!");
        
        int c = Color.CYAN;
        
        vwParentRow.setBackgroundColor(c); 
        vwParentRow.refreshDrawableState();       
    }


In this case, the View v being passed in as a parameter is our button.
We get the parent of our button, being careful to cast it as a LinearLayout (have another look at the xml if you're not sure why), and then simply set the background colour of our Layout Row.

Don't forget to call .refreshDrawableState(); on your vwParentRow or it will never redraw and you won't see your nice colour change.

.. Tada!




Update: Hi everyone, thanks for all the interest, here's a link to the full project zipped.
Updated Update: Here's another link, and another, and another.

I've also updated the above myClickHandler to loop through the other items in the ListView and reset them to a default colour, in this case blue, to make it a bit more obvious what's going on. Hope that helps.

Thursday, February 18, 2010

How to create Status Bar Notifications


A status bar notification is used to notify the user of a system event, like an sms being received or a new device being detected, without interrupting the user from whatever other task they might be doing with their phone.

The status bar area is located at the top of the screen and the user can pull this area to expand it, and show a history of notifications.





If a Notification has been setup to include an enclosed Intent, selecting that particular notification in this expanded view can fire the Intent, taking the user to an Activity screen of the related application, or do any of the many other things that Intents can do.

You can also configure the notification to alert the user with a sound, a vibration, and flashing lights on the device.

A background Service should never launch an Activity on its own in order to receive user interaction. The Service should instead create a status bar notification that will launch the Activity when selected by the user.

A status bar notification should be used for any case in which a background Service needs to alert the user about an event.


Here is a simple method that creates and displays a notification when passed a string msg :


public void displayNotification(String msg)
{
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(R.drawable.icon, msg, System.currentTimeMillis());

// The PendingIntent will launch activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, REQUEST_CODE, new Intent(this, ExpandNotification.class), 0);

notification.setLatestEventInfo(this, "Title here", ".. And here's some more details..", contentIntent);

manager.notify(NOTIFICATION_ID, notification);

}


.. Which you can call simply like this:


displayNotification("Hi, I'm a Statusbar Notification..");



.. Easy, isn't it?

Sunday, February 14, 2010

Want to display a quick message to your users? Use some Toast!



A toast is a notification view that contains a quick little message for the user. All it does is display the message, it's not interactive at all.

The toast class helps you create and show those.

When this view is shown to the user, it appears to float over the application. It will never receive focus as the user may be in the middle of typing something else. The idea is to be as unobtrusive as possible, while still showing the user the information you want them to see. Two possible examples are a volume control, and a brief message saying that your settings have been saved.

The easiest way to use this class is to call one of the static methods that constructs everything you need and returns a new Toast object.

A piece of toast is typically a simple text message, an image, or a combination of text and image.
You don't have control on the toast duration. There are only two states LENGTH_LONG and LENGTH_SHORT.

For this example we have added an image to one of the drawable folders under 'res' in our project, we refer to it simply like this:

view.setImageResource(R.drawable.flash);

Our image is actually called flash.png, note that we don't need to include the 'png' extension in our Android code, it already knows what we're talking about ;)

Here are 3 methods, demonstrating the above.

First, just show an image:

private void imageToast()
{
Toast toast = new Toast(getApplicationContext());
ImageView view = new ImageView(getApplicationContext());
view.setImageResource(R.drawable.flash);
toast.setView(view);
toast.show();
}


This method just displays a text toast:

public void textToast(String textToDisplay) {
Context context = getApplicationContext();
CharSequence text = textToDisplay;
int duration = Toast.LENGTH_SHORT;

Toast toast = Toast.makeText(context, text, duration);
toast.setGravity(Gravity.TOP|Gravity.LEFT, 50, 50);
toast.show();
}


.. And this method displays a Toast that contains both an image and text:

public void textAndImageToast(String textToDisplay)
{
Toast toast = Toast.makeText(getApplicationContext(), textToDisplay, Toast.LENGTH_LONG);
View textView = toast.getView();
LinearLayout lay = new LinearLayout(getApplicationContext());
lay.setOrientation(LinearLayout.HORIZONTAL);
ImageView view = new ImageView(getApplicationContext());
view.setImageResource(R.drawable.flash);
lay.addView(view);
lay.addView(textView);
toast.setView(lay);
toast.show();
}

You can call each of these like this:

textToast("This is a text toast");
        imageToast();

textAndImageToast("This is a text and image toast");


And here are the results:

A text Toast



A text & image Toast


this is an image Toast


Here is the same image Toast with default Gravity for better demonstration purposes


Hope that makes sense.
.. Till next time, happy coding :)


Monday, February 1, 2010

How to populate a Spinner widget from a database


Yesterday we saw how to populate a Spinner widget from an Array, and today, as promised, I'll show you how to populate a Spinner widget from a database.

This tutorial assumes that you're using the same code we used previously (link above), essentially the only differences are that we're using a different type of Adapter (a SimpleCursorAdapter this time) and populating it with the results of a query from a database table, and we're using a separate layout item to put our colour names


Here is our new layout, called db_view_row.xml:

<LinearLayout android:id="@+id/LinearLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">

<TextView android:text=""
android:id="@+id/tvDBViewRow"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
</LinearLayout>


put this file in your res/layout directory in your project.


Let's assume for this example that you've already populated your database table with the same list of colours that we used previously.

Here's the statement to create our table structure:


CREATE TABLE "colours" (
"_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"colourName" TEXT NOT NULL
)




In our database adapter class we have a method called fetchAllColours(), which has the responsibility (probably no surprises here) of fetching all our colours ;).


At the top of our class we declare some static variables we're going to use :


public class TestDBAdapter {

public static final String KEY_TITLE = "colourName";
public static final String KEY_ROWID = "_id";

//..rest of class continues from here..



Here is this method also from our TestDBAdapter class:

public Cursor fetchAllColours()
{
if (mDb == null)
{
this.open();
}


/* here we check if our db exists as the connection might have been closed unexpectedly... and open it if it doesn't already exist*/


String tableName = "colours";

return mDb.query(tableName, new String[] { KEY_ROWID, KEY_TITLE}, null, null, null, null, null);

}



... which returns a database Cursor to our method below..


private void fillData() {
Cursor coloursCursor;
Spinner colourSpinner = (Spinner) findViewById(R.id.my_colour_spinner);
coloursCursor = thisTestDBAdapter.fetchAllColours();

startManagingCursor(
coloursCursor);
/*Create an array to specify the fields we want to display in the list (only the 'colourName' column in this case) */

String[] from = new String[]{TestDBAdapter.KEY_TITLE};

/* and an array of the fields we want to bind those fields to (in this case just the textView 'tvDBViewRow' from our new
db_view_row.xml layout above) */

int[] to = new int[]{R.id.tvDBViewRow};

/*
Now create a simple cursor adapter.. */

SimpleCursorAdapter colourAdapter =
new SimpleCursorAdapter(this, R.layout.db_view_row,
coloursCursor, from, to);

/* and assign it to our Spinner widget */

colourSpinner.setAdapter(colourAdapter);
}





Update: Due to popular demand, here is a project that demonstrates this concept:
Spinner from Database example.


.. Let me know if you're having any access issues, it's just hosted publicly from my google docs.


I've learnt my lesson, I'm going to be including an example project with all future posts :)

Sunday, January 31, 2010

How to populate a Spinner widget from an Array

Lets say we have an Array of colours, defined in a xml file in our res folder in our Eclipse project, like below.

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello World, droidTest1!</string>
<string name="app_name">droidTest1</string>

<string-array name="colours">
<item>Red</item>
<item>Blue</item>
<item>White</item>
<item>Yellow</item>
<item>Black</item>
<item>Green</item>
<item>Purple</item>
<item>Orange</item>
<item>Grey</item>
</string-array>
</resources>

Spinners are Android's implementation of a dropdown menu, of sorts.
How do we insert these values into a spinner widget?

First, we need to create a layout for our Spinner widget, so it has a user interface.
We could do this is code, but the more accepted method is to define our layouts for Android applications in XML files, located in our res/layout folder in our project.

<Spinner

android:id="@+id/my_colour_spinner"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:drawSelectorOnTop="true”

/>




Then we create an instance of the Spinner class,

Spinner colourSpinner = (Spinner) findViewById(R.id.my_colour_spinner);



Next we create an ArrayAdapter to wrap our xml array (Array of colours) and we put some pre-defined styles.



ArrayAdapter adapter = ArrayAdapter.createFromResource( this, R.array.colors, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);


And we set the ArrayAdapter to our instance of the Spinner class:
colourSpinner.setAdapter(adapter);


And when we run our application:

The closed Spinner Widget



.. When selected shows us out colour options.





Aaah.. Sweet Androidy Success!


Next..

Sunday, January 24, 2010

Creating multiple sqlite database tables in Android



Most of the Android database examples you will find on the web will usually contain only one table to demonstrate the basic database concepts.

That's great, the only problem with this is that most non-trivial database implementations will contain more than one table.


The standard database creation string for a single table will probably look a lot like the below:


private static final String CREATE_TABLE_1 =
" create table " + table1 +
" (_id integer primary key autoincrement," +
" title text not null, body text not null);";



Which is called in your DB Adapter class like this:


@Override
public void onCreate(SQLiteDatabase db) {

db.execSQL(CREATE_TABLE_1);
}


So what to do if you want to create more than one table?
You may do the below.. but will it work?
Note that this is one big string containing three separate create statements...


private static final String DATABASE_CREATE_MULTIPLE_TABLES =
" create table " + ITEMS_TABLE +
" (_id integer primary key autoincrement," +
" title text not null)" +

" create table " + TAGS_TABLE +
" (_id integer primary key autoincrement," +
" tagName text not null)" +

" create table " + LOCATIONS_TABLE +
" (_id integer primary key autoincrement," +
" locationName text not null, gpsCoOrds text);"
;


And then you try calling them in your DB Adapter class like so:


@Override
public void onCreate(SQLiteDatabase db) {

db.execSQL(DATABASE_CREATE_MULTIPLE_TABLES);
}


So you're trying to create the three tables in one call to db.execSQL.


This appears to compile and run successfully, you can even read and write to the FIRST table that is created, but..
..when you try to read or write to any other table you will see the dreaded

'Sorry! The application Kdkddfblah (process test.Kdkddfblah) has stopped unexpectedly. Please try again'

..error message.


Uh-oh.

If you debug your application, you might see references to syntax errors 'near create', and possible a reference that the table doesn't exist.

Hmm... What went wrong?

The answer is that sqlite, and therefore the db.exec method, only lets you execute one sql command at a time. We were trying to run three sql statements in one go in db.execSQL(DATABASE_CREATE_MULTIPLE_TABLES);.

So what you need to do to fix this is move each of the above table create statements into their own strings, like this (Note that this now creates three seperate strings, unlike above):


private static final String CREATE_TABLE_1 =
" create table " + table1 +
" (_id integer primary key autoincrement," +
" title text not null, body text not null);";

private static final String CREATE_TABLE_2 =
" create table " + TAGS_TABLE +
" (_id integer primary key autoincrement," +
" tagName text not null)";

private static final String CREATE_TABLE_3 =
" create table " + LOCATIONS_TABLE +
" (_id integer primary key autoincrement," +
" locationName text not null, gpsCoOrds text);";


.. and then use these strings in your onCreate method like below, this then works.


@Override
public void onCreate(SQLiteDatabase db) {

db.execSQL(CREATE_TABLE_1);
db.execSQL(CREATE_TABLE_2);
db.execSQL(CREATE_TABLE_3);
}


If you find this doesn't work for you, try dropping all the tables in your database and try again, or give the database a different name, or different version number. The SQLiteOpenHelper seems to have some troubles registering that the database is to be changed. It finds a db with the same name and version number, goes 'meh' and doesn't look to see if the structure is different at all.

You can also pull the sqlite db file right off your device (or emulator) by going into the DDMS perspective in Eclipse (Window menu\ Open perspective \ Other \ DDMS), navigating to the database file which will probably be at \data\data\*Your Application Name*\databases.

There's a 'pull file' button on the top left as seen highlighted below:



.. You can then open the DB in your favourite sqlite manager (I like Sqliteman) and play around. Can can also of course, push the file back to the device if you wish.




Monday, January 18, 2010

Psst..Remember that your layout files must have lowercase names..

Remember that your layout files must have lowercase names, or they won't show up in your autoComplete list of options after 'R.layout' in Eclipse when you try this :

setContentView(R.layout.test_db);


The file won't actually show up as having any errors in your package explorer (on the right by default in the IDE), but if you look down in the console (by default down the bottom), you'll see this:

Invalid file name: must contain only [a-z0-9_.]


You might see an error on your project name, but with all the folders and files it can be hard to track down the cause.

When you try to run your application you will see:

'Your project contains error(s) please fix them before running your application'


.. and it won't be happy until you delete the offending file, even if you're not actively referencing it in your code.



Hope that helps.. Happy coding!