Search code examples
androidrestandroid-studioandroid-annotations

AndroidAnnotations using REST with BaseAdapter throw 'Can't create handler inside thread that has not called Looper.prepare()'


UPDATE: HomeActivity.java code has been updated as suggested by WonderCsabo

I'm newbie on Android development. My scenario is load REST API (from @Rest) from server & load to BaseAdapter.

When I run it, no data is loaded & it just throw to exception message Can't create handler inside thread that has not called Looper.prepare() (see that I use Log.e to trace error exception message). Below is my code:

Product.java

public class Product {
    private int id;
    private String name;
    private String imageUrl;
    private int availableStock;
    private double purchasingPrice;
    private double sellingPrice;

    public Product() {}

    ... hide getter & setter for brevity
}

ProductAPI.java

@Rest(rootUrl = "http://192.168.42.154:1337/api/v1", converters = {GsonHttpMessageConverter.class, MappingJacksonHttpMessageConverter.class })
public interface ProductAPI extends RestClientErrorHandling {

    @Get("/products")
    List<Product> getProducts();

}

ProductListAdapter .java

public class ProductListAdapter extends BaseAdapter {

    List<Product> products;


    public ProductListAdapter(List<Product> products) {
        this.products = products;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (convertView == null) {
            convertView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.grid_product, parent, false);
        }

        String imageUrl = "http://lorempixel.com/800/600/sports/" + String.valueOf(position + 1);
        convertView.setTag(imageUrl);

        ImageView image = (ImageView) convertView.findViewById(R.id.image);
        Picasso.with(convertView.getContext())
                .load(imageUrl)
                .into(image);


        TextView name = (TextView) convertView.findViewById(R.id.name);
        name.setText(getItem(position).toString());

        return convertView;
    }

    @Override
    public int getCount() {
        return products.size();
    }

    @Override
    public Object getItem(int position) {
        return products.get(position);
    }

    @Override
    public long getItemId(int position) {
        return products.indexOf(getItem(position));
    }
}

HomeActivity.java

@EActivity(R.layout.activity_home)
@OptionsMenu(R.menu.main)
public class HomeActivity extends BaseActivity {

    List<Product> products;
    ProductListAdapter adapter;

    @ViewById
    DrawerLayout drawer;

    @ViewById
    GridView gridView;

    @Bean
    CustomRestErrorHandler restErrorHandler;

    @RestService
    ProductAPI productAPI;

    @Background
    void searchProducts() {
        try {
            products = productAPI.getProducts();
            initAdapter();
            Log.d("test", String.valueOf(products.size()));
        } catch (Exception e) {
            Log.d("test", e.getMessage());
        }
    }

    @UiThread
    void initAdapter() {
        adapter = new ProductListAdapter(products);
    }

    @AfterViews
    void initDrawer() {
        setActionBarIcon(R.drawable.ic_ab_drawer);
        drawer.setDrawerShadow(R.drawable.drawer_shadow, Gravity.START);
        productAPI.setRestErrorHandler(restErrorHandler);
    }

    @AfterViews
    void bindAdapter() {
        searchProducts();

        gridView.setAdapter(adapter);
        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                String url = (String) view.getTag();
                Map<String,String> intents = new HashMap<>();

                intents.put("productName", adapter.getItem(i).toString());
                intents.put("DetailActivity:image", url);

                DetailActivity_.launch(HomeActivity.this, view.findViewById(R.id.image), intents);
            }
        });
    }
}

Below is the response from http://192.168.42.154:1337/api/v1/products:

[
  {
    "name": "Product 1",
    "imageUrl": "0d47e87c-ec21-4aa5-a051-cdd051d4f1e9.png",
    "availableStock": 23,
    "purchasingPrice": 135000,
    "sellingPrice": 150000,
    "id": 1,
    "createdAt": "2015-01-05T13:00:14.212Z",
    "updatedAt": "2015-01-05T13:00:14.212Z"
  },
  {
    "name": "Product 2",
    "imageUrl": "02cc5c02-2654-4d6c-ab96-e4179b2bb967.jpg",
    "availableStock": 7,
    "purchasingPrice": 45000,
    "sellingPrice": 60000,
    "id": 2,
    "createdAt": "2015-01-05T13:00:40.451Z",
    "updatedAt": "2015-01-05T13:00:40.451Z"
  }
]

I'm so happy if everyone help me :)

UPDATE: Complete stacktrace

01-07 08:36:09.818  14122-14124/com.pewh.materialeverywhere D/dalvikvm﹕ GC_CONCURRENT freed 355K, 11% free 9756K/10951K, paused 20ms+13ms, total 69ms
01-07 08:36:20.749  14504-14508/com.pewh.materialeverywhere D/dalvikvm﹕ GC_CONCURRENT freed 281K, 11% free 9505K/10567K, paused 19ms+18ms, total 99ms
01-07 08:36:20.749  14504-14504/com.pewh.materialeverywhere D/dalvikvm﹕ WAIT_FOR_CONCURRENT_GC blocked 4ms
01-07 08:36:20.809  14504-14504/com.pewh.materialeverywhere I/dalvikvm﹕ Could not find method android.view.ViewGroup.onRtlPropertiesChanged, referenced from method android.support.v7.widget.Toolbar.onRtlPropertiesChanged
01-07 08:36:20.809  14504-14504/com.pewh.materialeverywhere W/dalvikvm﹕ VFY: unable to resolve virtual method 11502: Landroid/view/ViewGroup;.onRtlPropertiesChanged (I)V
01-07 08:36:20.809  14504-14504/com.pewh.materialeverywhere D/dalvikvm﹕ VFY: replacing opcode 0x6f at 0x0007
01-07 08:36:20.819  14504-14504/com.pewh.materialeverywhere I/dalvikvm﹕ Could not find method android.content.res.TypedArray.getChangingConfigurations, referenced from method android.support.v7.internal.widget.TintTypedArray.getChangingConfigurations
01-07 08:36:20.819  14504-14504/com.pewh.materialeverywhere W/dalvikvm﹕ VFY: unable to resolve virtual method 420: Landroid/content/res/TypedArray;.getChangingConfigurations ()I
01-07 08:36:20.819  14504-14504/com.pewh.materialeverywhere D/dalvikvm﹕ VFY: replacing opcode 0x6e at 0x0002
01-07 08:36:20.819  14504-14504/com.pewh.materialeverywhere I/dalvikvm﹕ Could not find method android.content.res.TypedArray.getType, referenced from method android.support.v7.internal.widget.TintTypedArray.getType
01-07 08:36:20.819  14504-14504/com.pewh.materialeverywhere W/dalvikvm﹕ VFY: unable to resolve virtual method 442: Landroid/content/res/TypedArray;.getType (I)I
01-07 08:36:20.819  14504-14504/com.pewh.materialeverywhere D/dalvikvm﹕ VFY: replacing opcode 0x6e at 0x0002
01-07 08:36:20.859  14504-14504/com.pewh.materialeverywhere D/AbsListView﹕ Get MotionRecognitionManager
01-07 08:36:20.869  14504-14504/com.pewh.materialeverywhere D/AbsListView﹕ Get MotionRecognitionManager
01-07 08:36:21.049  14504-14508/com.pewh.materialeverywhere D/dalvikvm﹕ GC_CONCURRENT freed 355K, 11% free 9687K/10823K, paused 18ms+3ms, total 92ms
01-07 08:36:21.049  14504-14522/com.pewh.materialeverywhere D/dalvikvm﹕ WAIT_FOR_CONCURRENT_GC blocked 24ms
01-07 08:36:21.059  14504-14515/com.pewh.materialeverywhere D/dalvikvm﹕ WAIT_FOR_CONCURRENT_GC blocked 26ms
01-07 08:36:21.059  14504-14504/com.pewh.materialeverywhere D/dalvikvm﹕ WAIT_FOR_CONCURRENT_GC blocked 26ms
01-07 08:36:21.059  14504-14504/com.pewh.materialeverywhere D/libEGL﹕ loaded /system/lib/egl/libEGL_mali.so
01-07 08:36:21.109  14504-14504/com.pewh.materialeverywhere D/libEGL﹕ loaded /system/lib/egl/libGLESv1_CM_mali.so
01-07 08:36:21.109  14504-14504/com.pewh.materialeverywhere D/libEGL﹕ loaded /system/lib/egl/libGLESv2_mali.so
01-07 08:36:21.149  14504-14504/com.pewh.materialeverywhere D/OpenGLRenderer﹕ Enabling debug mode 0
01-07 08:36:24.232  14504-14522/com.pewh.materialeverywhere E/com.pewh.materialeverywhere﹕ Can't create handler inside thread that has not called Looper.prepare()

Solution

  • The culprit is here:

    searchProducts();
    adapter = new ProductListAdapter(products);
    

    searchProducts() is a @Background annotated method, which means it will do its work on a background thread. While bindAdapter() will continue execution on the main thread. It means products is still null when you call the constructor, so it will pass a null, and that's why NullPointerException is thrown in getCount(). You have to update your adapter after searchProducts() is done, for example:

    @Background
    void searchProducts() {
        try {
            products = productAPI.getProducts();
            initAdapter();
            Log.d("test", "the size is " + products.size());
        } catch (Exception e) {
            Log.d("test", e.getMessage());
        }
    }
    
    @UiThread // will be called on the main thread
    void initAdapter() {
        adapter = new ProductListAdapter(products);
        ...
    }
    

    EDIT:

    First, you should move all adapter initialization code to initAdapter(). At this point:

    @AfterViews
    void bindAdapter() {
        searchProducts();
    
        gridView.setAdapter(adapter);
        ...
    

    adapter is still null!

    Second: Can't create handler inside thread that has not called Looper.prepare(), mostly is a symptom of running UI-related operations on a background thread. Please be sure you are not doing anything UI-related in a @Background annotated method.