Labels

Tuesday, June 28, 2011

Prompt Dialog for Android

Reference: Prompt User Input with an AlertDialog

I've written a helper class, that makes it easy and fast to create prompt-dialogs on Android.
Usage:
PromptDialog dlg = new PromptDialog(MainActivity.this, R.string.title, R.string.enter_comment) {
 @Override
 public boolean onOkClicked(String input) {
  // do something
  return true; // true = close dialog
 }
};
dlg.show();

You just have to create a new class PromptDialog:
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.widget.EditText;

/**
 * helper for Prompt-Dialog creation
 */
public abstract class PromptDialog extends AlertDialog.Builder implements OnClickListener {
 private final EditText input;

 /**
  * @param context
  * @param title resource id
  * @param message resource id
  */
 public PromptDialog(Context context, int title, int message) {
  super(context);
  setTitle(title);
  setMessage(message);

  input = new EditText(context);
  setView(input);

  setPositiveButton(R.string.ok, this);
  setNegativeButton(R.string.cancel, this);
 }

 /**
  * will be called when "cancel" pressed.
  * closes the dialog.
  * can be overridden.
  * @param dialog
  */
 public void onCancelClicked(DialogInterface dialog) {
  dialog.dismiss();
 }

 @Override
 public void onClick(DialogInterface dialog, int which) {
  if (which == DialogInterface.BUTTON_POSITIVE) {
   if (onOkClicked(input.getText().toString())) {
    dialog.dismiss();
   }
  } else {
   onCancelClicked(dialog);
  }
 }

 /**
  * called when "ok" pressed.
  * @param input
  * @return true, if the dialog should be closed. false, if not.
  */
 abstract public boolean onOkClicked(String input);
}

Monday, June 27, 2011

Play ringtone preference from service

Reference: android - How to get ringtone preference at runtime??? - Stack Overflow

First, add a Ringtone Preference to your preferences.xml.
<RingtonePreference android:title="@string/ringtone" android:ringtoneType="notification" android:showSilent="true" android:key="push_ringtone" android:showDefault="true"></RingtonePreference>

Now you can give your notification this ringtone as sound attribute.
SharedPreferences preference = PreferenceManager.getDefaultSharedPreferences(this);
String strRingtonePreference = preference.getString("push_ringtone", "DEFAULT_SOUND");
notification.sound = Uri.parse(strRingtonePreference);

Wednesday, June 22, 2011

Autostart Service on Device Boot

Reference: My life with Android :-): Autostarting services

It's possible to register your own application service for starting automatically when the device has been booted. You need this, for example, when you want to receive push events from a http server and want to inform the user as soon a new event occurs. The user doesn't have to start the activity manually before the service get started...

It's quite simple. First give your app the permission RECEIVE_BOOT_COMPLETED.
Next you need to register a BroadcastReveiver. We call it BootCompletedIntentReceiver.

Your Manifest.xml should now look like this:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.jjoe64">
 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
 <application>
  <receiver android:name=".BootCompletedIntentReceiver">
   <intent-filter>
    <action android:name="android.intent.action.BOOT_COMPLETED" />
   </intent-filter>
  </receiver>
  <service android:name=".BackgroundService"/>
 </application>
</manifest>

As the last step you have to implement the Receiver. This receiver just starts your background service.
package com.jjoe64;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

import com.jjoe64.BackgroundService;

public class BootCompletedIntentReceiver extends BroadcastReceiver {
 @Override
 public void onReceive(Context context, Intent intent) {
  if ("android.intent.action.BOOT_COMPLETED".equals(intent.getAction())) {
   Intent pushIntent = new Intent(context, BackgroundService.class);
   context.startService(pushIntent);
  }
 }
}

That's all ;-)

Tuesday, June 21, 2011

How to fix Activity.performStop NullPointer Exception

Just now I get a strange NullPointerException when I want to open my preferences activity in my app.

Logcat says:
E/AndroidRuntime(11226): FATAL EXCEPTION: main
E/AndroidRuntime(11226): java.lang.RuntimeException: Unable to stop activity {com.jjoe64.tests/com.jjoe64.tests.MainActivity}: java.lang.NullPointerException
E/AndroidRuntime(11226):  at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:2430)
E/AndroidRuntime(11226):  at android.app.ActivityThread.handleStopActivity(ActivityThread.java:2475)
E/AndroidRuntime(11226):  at android.app.ActivityThread.access$1800(ActivityThread.java:117)
E/AndroidRuntime(11226):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:948)
E/AndroidRuntime(11226):  at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(11226):  at android.os.Looper.loop(Looper.java:130)
E/AndroidRuntime(11226):  at android.app.ActivityThread.main(ActivityThread.java:3683)
E/AndroidRuntime(11226):  at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(11226):  at java.lang.reflect.Method.invoke(Method.java:507)
E/AndroidRuntime(11226):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
E/AndroidRuntime(11226):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
E/AndroidRuntime(11226):  at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime(11226): Caused by: java.lang.NullPointerException
E/AndroidRuntime(11226):  at android.app.Activity.performStop(Activity.java:3885)
E/AndroidRuntime(11226):  at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:2427)
E/AndroidRuntime(11226):  ... 11 more

There's no method called, which was written by me. So, is it an Android bug?
The best idea now is to take a look in the source code of Android:
final void performStop() {
//[...]    
 synchronized (mManagedCursors) {
  final int N = mManagedCursors.size();
  for (int i=0; i<N; i++) {
   ManagedCursor mc = mManagedCursors.get(i);
   if (!mc.mReleased) {
   mc.mCursor.deactivate(); // -------> NullPointer
   mc.mReleased = true;
  }
 }
//[...]
}

It's clear, that the problem is that a cursor is "null".
So the reason of that issue was found fast: Never return "null" in your content provider's query method.
Instead of null, you should throw an exception or return an empty MatrixCursor like that:
return new MatrixCursor(new String[] {"_id"}); // never return null

A really nice way to detect, null-cursor managing, is to override the startManagingCursor method:
 @Override
 public void startManagingCursor(Cursor c) {
  if (c == null) {
   throw new IllegalStateException("cannot manage cursor: cursor == null");
  }
  super.startManagingCursor(c);
 }

Saturday, June 18, 2011

Android Hackathon: Hello Renderscript

I was at the Android Dev Camp Stuttgart 2011. In the Hackathon session, we decided to check out the new renderscript engine for 2-3 hours.
Sadly this engine is not well documented yet and so my result is not very impressive, but okay...

The app shows a white surface and when you touch on the screen the surface will smoothly jump to that point.
me and marc during the presentation
The source code for this app is hosted on github.
https://github.com/jjoe64/Hello-Renderscript

Friday, June 17, 2011

How to fix WindowManager android.view.WindowLeaked Errors

I always try to build my apps with clearly and warnings-free source code. But from day to day my app growed and suddenly I noticed that error in the logcat:

E/WindowManager( 1374): Activity com.jjoe64.example.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@405446f8 that was originally added here
E/WindowManager( 1374): android.view.WindowLeaked: Activity com.jjoe64.example.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@405446f8 that was originally added here
E/WindowManager( 1374):  at android.view.ViewRoot.(ViewRoot.java:258)
E/WindowManager( 1374):  at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:148)
E/WindowManager( 1374):  at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
E/WindowManager( 1374):  at android.view.Window$LocalWindowManager.addView(Window.java:424)
E/WindowManager( 1374):  at android.app.Dialog.show(Dialog.java:241)
E/WindowManager( 1374):  at com.jjoe64.example.MainActivity.showLoading(MainActivity.java:682)
E/WindowManager( 1374):  at com.jjoe64.example.view.HieraticalHostListView.loadData(HieraticalHostListView.java:84)
E/WindowManager( 1374):  at com.jjoe64.example.MainActivity.setupListViewStatus(MainActivity.java:479)
E/WindowManager( 1374):  at com.jjoe64.example.MainActivity.checkZabbixLogin(MainActivity.java:162)
E/WindowManager( 1374):  at com.jjoe64.example.MainActivity.onResume(MainActivity.java:279)
E/WindowManager( 1374):  at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1150)
E/WindowManager( 1374):  at android.app.Activity.performResume(Activity.java:3832)
E/WindowManager( 1374):  at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2110)
E/WindowManager( 1374):  at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2135)
E/WindowManager( 1374):  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1668)
E/WindowManager( 1374):  at android.app.ActivityThread.access$1500(ActivityThread.java:117)
E/WindowManager( 1374):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
E/WindowManager( 1374):  at android.os.Handler.dispatchMessage(Handler.java:99)
E/WindowManager( 1374):  at android.os.Looper.loop(Looper.java:123)
E/WindowManager( 1374):  at android.app.ActivityThread.main(ActivityThread.java:3683)
E/WindowManager( 1374):  at java.lang.reflect.Method.invokeNative(Native Method)
E/WindowManager( 1374):  at java.lang.reflect.Method.invoke(Method.java:507)
E/WindowManager( 1374):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
E/WindowManager( 1374):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
E/WindowManager( 1374):  at dalvik.system.NativeStart.main(Native Method)

This error occurs when I change the orientation or when I bring the app into the background.

I shortly googled for that error but I didn't find anything and my app works well with this error. But today I tried to fix this badness again, and here I go:

The problem was, that I created a ProgressDialog and later just called ProgressDialog#hide.

The simple solution:
In Activity#onStop I have to call ProgressDialog#dismiss.

@Override
 protected void onStop() {
  super.onStop();
  if (loadingDlg != null) {
   loadingDlg.dismiss();
   loadingDlg = null;
  }
 }

Thursday, June 16, 2011

Using Janrain (rpx) login with Ruby on Rails

Janrain makes it possible, that users can sign in your app with any open-id provider (google, yahoo, ...).

Here's is a source code cutout of my app, that shows you how to implement a Janrain login in rails 3.



skip_before_filter :verify_authenticity_token, :only=>['rpx']

def rpx
 require 'net/http'
 require 'net/https'

 # login janrain callback
 # token
 token = request[:token]
 apiKey = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' #### replace this with your api key
 # http request auth_info
 host = 'rpxnow.com'
 path = '/api/v2/auth_info?apiKey='+apiKey+'&token='+token
 http = Net::HTTP.new(host, 443)
 http.use_ssl = true
 resp, result = http.get(path)

 # identifier
 data = ActiveSupport::JSON.decode result
 if data['stat'] == 'ok'
  # login successfull
  puts data.inspect
  email = data['profile']['email']
  display_name = data['profile']['name']['givenName'] + ' ' + data['profile']['name']['familyName']
  # is this a known user?
  u = EndCustomer.find(:first, :conditions=>["email = ?", email])
  if u
   # known user, do login
   _set_user_login u
  else
   # unknown
   if display_name.length < 2
    display_name = "-unbekannt-"
   end
   u = EndCustomer.new({:email => email, :display_name => display_name})
   u.bypass_password_validation
   if u.save
    _set_user_login u
   else
    flash[:warning] = 'Fehler bei der RPX-Anmeldung. Wenden Sie sich bitte an den Support.'
    format.html { render :action => "login" }
   end
  end
 else
  # failure
  flash[:warning] = 'Fehler bei der RPX-Anmeldung. Wenden Sie sich bitte an den Support.'
  format.html { render :action => "login" }
 end
end

Of course, you have to setup a route to YourController#rpx.

More information about janrain at www.janrain.com.