Reputation: 389
In my DataTrak activity I defined the following method:
public void updateTotal(IAmount totalAmount, int transactionType) {
switch (transactionType) {
case AccountTotals.VALUE_CANCELS:
txtView_cancels_value.setText("" + (Long.parseLong(txtView_cancels_value.getText().toString()) + totalAmount.getValue()));
break;
case AccountTotals.VALUE_PAYS:
txtView_pays_value.setText("" + (Long.parseLong(txtView_pays_value.getText().toString()) + totalAmount.getValue()));
break;
case AccountTotals.VALUE_SALES:
txtView_sales_value.setText("" + (Long.parseLong(txtView_sales_value.getText().toString()) + totalAmount.getValue()));
break;
default:
break;
}
btn_total.setBackgroundResource(R.drawable.button_green );
}
The method computes and updates some TextViews and changes the color of a button. Then I need to call this method from a Java abstract class. The method call appears in a method that runs on a non-UI thread. Here's how I call the method:
new Thread()
{
public void run()
{
new DataTrak().runOnUiThread(new Runnable()
{
public void run()
{
new DataTrak().updateTotal(totalAmount,transactionType);
}
});
}
}.start();
The problem is that I get a run time exception. Here's the LogCat output:
05-13 16:52:13.325: E/AndroidRuntime(22101): FATAL EXCEPTION: Thread-1577
05-13 16:52:13.325: E/AndroidRuntime(22101): Process: com.ilts.lct, PID: 22101
05-13 16:52:13.325: E/AndroidRuntime(22101): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
05-13 16:52:13.325: E/AndroidRuntime(22101): at android.os.Handler.<init> (Handler.java:200)
05-13 16:52:13.325: E/AndroidRuntime(22101): at android.os.Handler.<init>(Handler.java:114)
05-13 16:52:13.325: E/AndroidRuntime(22101): at android.app.Activity.<init>(Activity.java:786)
05-13 16:52:13.325: E/AndroidRuntime(22101): at com.ilts.lct.ap.mainframe.DataTrak.<init>(DataTrak.java:37)
05-13 16:52:13.325: E/AndroidRuntime(22101): at com.ilts.lct.ap.customerfunctions.CustomerTransaction$2.run(CustomerTransaction.java:736)
In fact, initially I had just the line with the method call but I got the same run time exception. I searched for "Can't create handler inside thread that has not called Looper.prepare()" and I found several posts on this issue. One of them made me put the method call inside a new Thread, as shown above. But I still get the same run time exception. What should I change? Could you help me understand what's the problem actually and how to fix it? Thanks in advance.
After I read the answer by @Metero here's the code in the Java abstract class:
public abstract class CustomerTransaction extends Transaction {
...................................................
public interface CallBack {
public void updateTotal(IAmount a,int n);
}
private static CallBack callback;
public static void registerCallback(CallBack callback1){
callback = callback1;
}
/**
* Method to update the transaction state
*
* @param state The new transaction state
**/
public void setState(final int state) {
this.state = state;
/*
* Update the status
*/
if (state == TRANSACTION_COMPLETE) {
new Thread()
{
public void run() {
Looper.prepare();
new DataTrak().runOnUiThread(new Runnable()
{
public void run() {
Log.i("HERE", "HERE");
Looper.prepare();
callback.updateTotal(totalAmount,transactionType);
Looper.loop();
}
});
}
}.start();
}
}
....................
}
Upvotes: 1
Views: 1429
Reputation: 13647
You should never instantiate an Activity by yourself, if you do that, it won't be a normal activity, it will be just a problematic plan Java object.
So what you can maybe to to solve your problem is use the Observer pattern where you can define an interface with the 'callback' method and you let the Activity implement it and make it subscribe to the 'provider' of the notification. So basically, when this update Thread is running, you will run thru the list of subscribed listeners and dispatch the call, it will be just like a normal method call.
Just keep in mind to: 'subscribe' and 'unsubscribe' respecting the Activity lifecycles..like subscribe on onCreate() and unsubscribe on onDestroy().
Activity:
public class YourActivity extends Activity implements ControlListener {
public void onCreate(...) {
....
control.registerListener(this);
control.performOperation();
}
public void onDestroy(...) {
....
control.unregisterListener(this);
}
public void updateTotal(String newValue) {
runOnUiThread(new Runnable() {
public void run() {
textView.setText(newValue);
}
});
}
}
Control class:
public class Control {
private Set<ControlListener> listeners = new HashSet<ControlListener>();
public synchronized void registerListener(ControlListener listener) {
listeners.add(listener);
}
public synchronized void unRegisterListener(ControlListener listener) {
listeners.remove(listener);
}
public synchronized void notifyListeners(String newValue) {
for(ControlListener listener : listeners) {
listener.updateTotal(newValue);
}
}
public void performOperation() {
new Thread() {
public void run() {
String newValue= service.performBackgroundOperationToGetNewValue();
notifyListeners(newValue);
}
}.start();
}
}
Control listener:
public interface ControlListener {
public void updateTotal(String newValue);
}
Alternatively, you can use a very HANDY library to apply the Observer pattern on your project, it's the Otto: http://square.github.io/otto/ With the Otto, you wouldn't need to have the register/unregister/notifylisteners methods in your control, it would be placed somewhere else automatically.
Upvotes: 1
Reputation: 1
I agree @Alécio, please use a callback to do this. Add a callback interface in the non-activity class:
class yourclass{
public interface callBack{
public void updateTotal(...);
}
private callBack callback;
public void registerCallback(callBack callback){
this.callback = callback;
}
//somewhere call the updateTotal(...)
callback.updateTotal(...);
}
In the Activity
//the activity implement the callback and then register it, and call the callback when neccesary
class yourActivity extends Activity implement callBack{
@Override
onCreate(...){
...
yourclass.registerCallback(this);
}
@Override
public void updateTotal(...){
.......
}
}
The sample code for multiple class comunicating.
public class Controller {
callBack callBack;
public void registerCallBack(callBack back){
this.callBack = back;
}
public void show(){
callBack.update(1, "my name");
}
public interface callBack{
public void update(int type, String message);
}
public callBack getCallBack(){
return callBack;
}
}
public class AnotherClass {
Controller controller = new Controller();
public void registerCallBack(callBack back){
controller.registerCallBack(back);
}
public void show(){
controller.getCallBack().update(1, "show me!");
}
}
public class MyActivity extends Activity implements callBack {
AnotherClass myclass = new AnotherClass();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("TAG", "This is message!");
setContentView(R.layout.main);
myclass.registerCallBack(this);
}
@Override
protected void onResume() {
super.onResume();
myclass.show();
}
@Override
public void update(int type, String message) {
((TextView) findViewById(R.id.display)).setText(message);
}
}
Upvotes: 0
Reputation: 628
The very nature of this question suggests a MVC violation. Instead of your model calling methods in an Activity, it should call a method on some callback that the Activity has registered on the model. This callback should be queued in the UI thread.
Upvotes: 1
Reputation: 1942
This is not the best choice.
new Thread()
{
public void run()
{
new DataTrak().runOnUiThread(new Runnable()
{
public void run()
{
Looper.prepare();//This is not the best choice.
new DataTrak().updateTotal(totalAmount,transactionType);
}
});
}
}.start();
I recommend use AsyncTask, like this.
class SampleTask extends AsyncTask<Boolean, Boolean, Boolean> {
private int totalAmount;
private yourActivity activity;
//passing parameters
public void SampleTask(yourActivity activity){
this.activity = activity;
}
@Override
protected Boolean doInBackground(Boolean... params) {
while (totalAmount < 10){
totalAmount++;
}
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
// this is not the best choice
// because you are creating two instances.
new DataTrak().updateTotal(totalAmount,transactionType);
//If you pass a parameter, this for me is the best option.
activity.updateTotal(totalAmount,transactionType);
}
}
Upvotes: 0