I wan't to test some open gl stuff in an android instrumentation unit test.
If I'm not mistaken all test are run inside the acual device so I thought opengl calls should work as well.
However this seams to be not the case, or I'm missing something ( so I hope ).
I stated a new project & a very simple test project to evaluete this.
So here are my tests:
package com.example.test;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import android.opengl.GLES20;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import com.example.HelloTesingActivity;
public class AndroidTest extends ActivityInstrumentationTestCase2<HelloTesingActivity> {
public AndroidTest(String pkg, Class<HelloTesingActivity> activityClass) {
super(pkg, activityClass);
}
private HelloTesingActivity mActivity;
public AndroidTest() {
super("com.example", HelloTesingActivity.class);
}
public void testTrue(){
assertTrue(true);
}
#Override
protected void setUp() throws Exception {
super.setUp();
mActivity = getActivity();
}
public void testPreConditions() throws Exception {
assertNotNull(mActivity); // passes
}
/*
* FAILS
*/
#UiThreadTest // ensures this test is run in the main UI thread.
public void testGlCreateTexture(){
IntBuffer buffer = newIntBuffer();
GLES20.glGenTextures(1, buffer);
assertFalse(buffer.get() == 0); // this fails
}
/**
* just a helper to setup a correct buffer for open gl to write the values into
* #return
*/
private IntBuffer newIntBuffer() {
ByteBuffer buff = ByteBuffer.allocateDirect(4);
buff.order(ByteOrder.nativeOrder());
buff.position(0);
return buff.asIntBuffer();
}
/*
* FAILS
*/
#UiThreadTest
public void testGlCalls(){
GLES20.glActiveTexture(1); // set the texture unit to 1 since 0 is the default case
IntBuffer value = newIntBuffer();
GLES20.glGetIntegerv(GLES20.GL_ACTIVE_TEXTURE, value );
assertEquals(1, value.get()); // this fails with expected: 1 but was: 0
}
}
And here is the activity itself, just for the sake of comletness.
package com.example;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
public class HelloTesingActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GLSurfaceView surface = new GLSurfaceView(this);
surface.setEGLContextClientVersion(2);
setContentView(surface);
surface.setRenderer(new MyRenderer());
}
}
package com.example;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView.Renderer;
public class MyRenderer implements Renderer {
#Override
public void onDrawFrame(GL10 arg0) {
// TODO Auto-generated method stub
}
#Override
public void onSurfaceChanged(GL10 arg0, int arg1, int arg2) {
// TODO Auto-generated method stub
}
#Override
public void onSurfaceCreated(GL10 arg0, EGLConfig arg1) {
// TODO Auto-generated method stub
}
}
I wanted to test GL code as well, here's how I'm doing it:
import java.util.concurrent.CountDownLatch;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.test.ActivityInstrumentationTestCase2;
/**
* <p>Extend ActivityInstrumentationTestCase2 for testing GL. Subclasses can
* use {#link #runOnGLThread(Runnable)} and {#link #getGL()} to test from the
* GL thread.</p>
*
* <p>Note: assumes a dummy activity, the test overrides the activity view and
* renderer. This framework is intended to test independent GL code.</p>
*
* #author Darrell Anderson
*/
public abstract class GLTestCase<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
private final Object mLock = new Object();
private Activity mActivity = null;
private GLSurfaceView mGLSurfaceView = null;
private GL10 mGL = null;
// ------------------------------------------------------------
// Expose GL context and GL thread.
// ------------------------------------------------------------
public GLSurfaceView getGLSurfaceView() {
return mGLSurfaceView;
}
public GL10 getGL() {
return mGL;
}
/**
* Run on the GL thread. Blocks until finished.
*/
public void runOnGLThreadAndWait(final Runnable runnable) throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
mGLSurfaceView.queueEvent(new Runnable() {
public void run() {
runnable.run();
latch.countDown();
}
});
latch.await(); // wait for runnable to finish
}
// ------------------------------------------------------------
// Normal users should not care about code below this point.
// ------------------------------------------------------------
public GLTestCase(String pkg, Class<T> activityClass) {
super(pkg, activityClass);
}
public GLTestCase(Class<T> activityClass) {
super(activityClass);
}
/**
* Dummy renderer, exposes the GL context for {#link #getGL()}.
*/
private class MockRenderer implements GLSurfaceView.Renderer {
#Override
public void onDrawFrame(GL10 gl) {
;
}
#Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
;
}
#Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
synchronized(mLock) {
mGL = gl;
mLock.notifyAll();
}
}
}
/**
* On the first call, set up the GL context.
*/
#Override
protected void setUp() throws Exception {
super.setUp();
// If the activity hasn't changed since last setUp, assume the
// surface is still there.
final Activity activity = getActivity(); // launches activity
if (activity == mActivity) {
mGLSurfaceView.onResume();
return; // same activity, assume surface is still there
}
// New or different activity, set up for GL.
mActivity = activity;
mGLSurfaceView = new GLSurfaceView(activity);
mGL = null;
// Attach the renderer to the view, and the view to the activity.
mGLSurfaceView.setRenderer(new MockRenderer());
activity.runOnUiThread(new Runnable() {
public void run() {
activity.setContentView(mGLSurfaceView);
}
});
// Wait for the renderer to get the GL context.
synchronized(mLock) {
while (mGL == null) {
mLock.wait();
}
}
}
#Override
protected void tearDown() throws Exception {
mGLSurfaceView.onPause();
super.tearDown();
}
}
You annotated your tests to run on the UIThread but OpenGL calls should be on the GLThread. AFAIK there's no annotation to run these tests.
Related
I am trying to create a native UI component for android in React Native.
The component, for now is a basic GLSurfaceView with a yellow background.
package com.test2;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import javax.microedition.khronos.egl.EGLConfig;
public class StitchSurface extends GLSurfaceView implements GLSurfaceView.Renderer {
public StitchSurface(Context context) {
super(context);
init();
}
public StitchSurface(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
setPreserveEGLContextOnPause(true);
setEGLConfigChooser(false);
setEGLContextClientVersion(2);
setRenderer(this);
}
#Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
}
#Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
#Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
}
I extended the SimpleViewManager
public class StitchSurfaceViewManager extends SimpleViewManager<StitchSurface> {
private static final String REACT_CLASS="RCTStitchSurface";
/**
* Subclasses should return a new View instance of the proper type.
*
* #param reactContext
*/
#Override
protected StitchSurface createViewInstance(ThemedReactContext reactContext) {
return new StitchSurface(reactContext);
}
/**
* #return the name of this view manager. This will be the name used to reference this view
* manager from JavaScript in createReactNativeComponentClass.
*/
#Override
public String getName() {
return REACT_CLASS;
}
}
and implemented the ReactPackage
public class StitchSurfaceViewPackage implements ReactPackage {
#Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
#Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
#Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new StitchSurfaceViewManager()
);
}
}
after Registering the Module in MainActivity.java and Implementing the JS module, when I use it in my index.android.js,
The GLSurfaceView renders as an empty box with red border.
Custom GLSurfaceView with dimension specified in a Stylesheet as (400,400)
Could you tell me what I could do to solve this?
The GLSurfaceView should render as a view with Yellow Background color.
When i am calling "private class Engine extends CanvasWatchFaceService.Engine", i'm getting the error of 'No enclosing instance of type 'android.support.wearable.watchface.CanvasWatchFaceService' is in scope'
I have called imported the class import android.support.wearable.watchface.CanvasWatchFaceService; but it says it's a unused import statement
UPDATED: This is the whole of my MyWatchFace.java
package com.projects.kainowitzke.googlewatchface;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceService;
import android.view.SurfaceHolder;
import android.support.wearable.watchface.CanvasWatchFaceService;
public class MyWatchface {
}
public class AnalogWatchFaceService extends MyWatchface {
#Override
public WatchFaceService.Engine onCreateEngine() {
/* provide your watch face implementation */
return new CanvasWatchFaceService.Engine();
}
/* implement service callback methods */
private class MyWatchfac extends CanvasWatchFaceService.Engine {
#Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
/* initialize your watch face */
}
#Override
public void onPropertiesChanged(Bundle properties) {
super.onPropertiesChanged(properties);
/* get device features (burn-in, low-bit ambient) */
}
#Override
public void onTimeTick() {
super.onTimeTick();
/* the time changed */
}
#Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
/* the wearable switched between modes */
}
#Override
public void onDraw(Canvas canvas, Rect bounds) {
/* draw your watch face */
}
#Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
/* the watch face became visible or invisible */
}
}
}
MyWatchface must extend CanvasWatchFaceService.
I've got this problem where when I change the orientation of my device, for some reason the on-screen representation of the ListView associated with my ListFragment is retained, and a new ListView is inflated beneath it. To give you some context, I am creating a very simple app whose purpose is to test some DAO objects I created. I would have preferred to post some screenshots depicting the behavior my app is experiencing, but unfortunately, my reputation is too low at the moment.
The best I could describe it would be that when I change the orientation of the device from portrait to landscape (and vice versa), it inflates a whole new ListView without deflating the previous one. I have been looking into this for a couple of days now, and quite honestly, I am stumped. Any help you guys could offer would be greatly appreciated.
Here is the code for the ListFragment:
package edu.uark.csce.mobile.healthyshopper;
import android.app.ListFragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
import android.content.Loader;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
/**
* A fragment representing a list of Items.
* <p />
* <p />
* Activities containing this fragment MUST implement the {#link Callbacks}
* interface.
*/
public class ViewFragment extends ListFragment implements
LoaderCallbacks<Cursor> {
// Class Fields:
private static final String TAG = "healthyshopper.ViewFragment";
private static final int MAIN_DB_LOADER = 0;
// In the final application, any query to the main database needs to return
// at least these fields:
private final String[] NECESSARY_COLUMNS = { DAOContentProvider.FOOD_DES,
DAOContentProvider.FOOD_CALORIES, DAOContentProvider.FOOD_FAT,
DAOContentProvider.FOOD_CARBS, DAOContentProvider.FOOD_PROTEIN };
private final String FROM[] = {DAOContentProvider.FOOD_GROUP, DAOContentProvider.FOOD_DES,
DAOContentProvider.FOOD_MANUFAC, DAOContentProvider.FOOD_PROTEIN,
DAOContentProvider.FOOD_FAT, DAOContentProvider.FOOD_CARBS,
DAOContentProvider.FOOD_CALORIES, DAOContentProvider.FOOD_SERV_SIZE};
private final int TO[] = {R.id.fd_group, R.id.shrt_desc,
R.id.manufac, R.id.protein,
R.id.fat, R.id.carb,
R.id.cal, R.id.amt};
private int threadMode;
private int queryType;
private int foodGroupId;
private CursorLoader loader;
private SimpleCursorAdapter listAdapter;
// Preference Option Constants:
private int UI_THREAD = 0;
private int BRANCHED_THREAD = 1;
private int ALL_ROWS = 0;
private int SPECIFIC_FD_GROUP = 1;
private int LIMITED_ROWS = 2;
private OnFragmentInteractionListener mListener;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public ViewFragment() {
}
/***************************************
* Life cycle callbacks:
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "in onCreate()");
setRetainInstance(true);
}
#Override
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
if(savedInstanceState == null){
listAdapter = new SimpleCursorAdapter(getActivity(), R.layout.list_node, null, FROM, TO, 0);
setListAdapter(listAdapter);
}
}
#Override
public void onStart(){
super.onStart();
}
#Override
public void onResume(){
super.onResume();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()
.getApplicationContext());
Log.d(TAG, preferences.getString("THREADING_MODE", "BLANK"));
//Load testing preferences:
threadMode = Integer.valueOf(preferences.getString("THREADING_MODE","0"));
queryType = Integer.valueOf(preferences.getString("QUERY_TYPE", "0"));
foodGroupId = Integer.valueOf(preferences.getString("FOOD_GROUP", "0")); // Default
if (threadMode == BRANCHED_THREAD) {
loader = (CursorLoader) getLoaderManager().initLoader(
MAIN_DB_LOADER, null, this);
} else {
String[] projection = null;
String selection = null;
if (queryType == SPECIFIC_FD_GROUP) {
Log.d(TAG, "Query Type: " + (getResources().getStringArray(R.array.query_type_select))[queryType]);
selection = DAOContentProvider.FOOD_GROUP + " = " + foodGroupId;
} else if (queryType == LIMITED_ROWS) {
projection = NECESSARY_COLUMNS;
}
/*This is for testing only. Ordinarily, you should NEVER perform Db operations on the UI thread.*/
listAdapter.swapCursor(getActivity().getContentResolver().query(
DAOContentProvider.CONTENT_URI, projection, selection, null, null));
}
}
#Override
public void onPause(){
super.onPause();
}
#Override
public void onStop(){
super.onStop();
}
#Override
public void onDestroyView(){
super.onDestroyView();
Log.e(TAG, "in onDestroyView()");
setListAdapter(null);
listAdapter.swapCursor(null);
listAdapter = null;
}
#Override
public void onDestroy(){
super.onDestroy();
}
#Override
public void onDetach(){
super.onDetach();
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
if (null != mListener) {
}
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated to
* the activity and potentially other fragments contained in that activity.
* <p>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
public void onFragmentInteraction(String id);
}
/**
* LoaderManager Callbacks;
*/
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case MAIN_DB_LOADER:
String[] projection = null;
String selection = null;
if (queryType == SPECIFIC_FD_GROUP) {
Log.d(TAG,
"Query Type: "
+ (getResources()
.getStringArray(R.array.query_type_select))[queryType]);
selection = DAOContentProvider.FOOD_GROUP + " = " + foodGroupId;
} else if (queryType == LIMITED_ROWS) {
projection = NECESSARY_COLUMNS;
}
return new CursorLoader(getActivity(),
DAOContentProvider.CONTENT_URI, projection, selection,
null, null);
default:
return null;
}
}
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor arg1) {
listAdapter.swapCursor(arg1);
listAdapter.notifyDataSetChanged();
}
#Override
public void onLoaderReset(Loader<Cursor> arg0) {
listAdapter.swapCursor(null);
listAdapter.notifyDataSetChanged();
}
}
And here is the code for its associated Activity:
package edu.uark.csce.mobile.healthyshopper;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class TestingActivity extends Activity implements ViewFragment.OnFragmentInteractionListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_testing);
ViewFragment list = new ViewFragment();
getFragmentManager().beginTransaction().add(R.id.test_layout, list).commit();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.testing, menu);
return true;
}
#Override
public void onFragmentInteraction(String id) {
// TODO Auto-generated method stub
}
}
You're using setRetainInstance(true); in your fragment. This makes the fragment to be retained and reused when the activity is re-created.
You can read more detailed description in this question: Further understanding setRetainInstance(true)
I am making a "Defend the castle" style android application. The game is complete, however I just need help closing my surfaceview and starting a new activity for when the the player has lost the game.
The condition for losing the game is just a boolean variable in my GameThread class. The variable is called "lost" and is by default set to false. When the life of the castle drops below 1, lost is set to true and a sound effect plays.
Ideally, I would like to stop the currently looping sound effects and open a new activity (which is already made and working) upon lost=true.
The main activity is as follows:
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
public class MainActivity extends Activity {
Button btn_startGame;
Activity activity;
GameView mGameView;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
activity = this;
setContentView(R.layout.main);
btn_startGame = (Button) findViewById(R.id.btnStartGame);
btn_startGame.setOnClickListener(new OnClickListener() {
//#Override
public void onClick(View v) {
mGameView = new GameView(activity);
setContentView(mGameView);
mGameView.mThread.doStart();
}
});
}
#Override
public boolean onTouchEvent(MotionEvent event) {
try {
mGameView.mThread.onTouch(event);
} catch(Exception e) {}
return true;
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
// ignore orientation/keyboard change
super.onConfigurationChanged(newConfig);
}
}
The surfaceview is created in this class called GameView:
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.SurfaceHolder.Callback;
public class GameView extends SurfaceView implements Callback {
Context mContext;
GameThread mThread;
public GameView(Context context) {
super(context);
this.mContext = context;
getHolder().addCallback(this);
mThread = new GameThread(getHolder(), mContext, new Handler() {
#Override
public void handleMessage(Message m) {
// Use for pushing back messages.
}
});
setFocusable(true);
}
//#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.d(this.getClass().toString(), "in SurfaceChanged()");
}
//#Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(this.getClass().toString(), "in SurfaceCreated()");
mThread.running = true;
mThread.start();
}
//#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(this.getClass().toString(), "in SurfaceDestroyed()");
boolean retry = true;
mThread.running = false;
while (retry) {
try {
mThread.join();
retry = false;
} catch (InterruptedException e) {
}
GameThread.music.stop();
GameThread.groan1.stop();
GameThread.groan2.stop();
GameThread.walk.stop();
GameThread.music.release();
GameThread.groan1.release();
GameThread.groan2.release();
GameThread.walk.release();
GameThread.shoot.release();
}
}
}
The GameThread class contains all of the drawing, the logic and all a run method (below).
#Override
public void run() {
// check if condition here
if(lost){
mContext.runOnUiThread(new Runnable() {
#Override
public void run() {
//start Activity here
Intent intent = new Intent(mContext, LoseActivity.class);
mContext.startActivity(intent);
}
});
}
else{
if (running == true) {
while (running) {
Canvas c = null;
try {
c = mHolder.lockCanvas();
if (width == 0) {
width = c.getWidth();
height = c.getHeight();
player.x = 50;
player.y = 45;
}
synchronized (mHolder) {
long now = System.currentTimeMillis();
update();
draw(c);
ifps++;
if (now > (mLastTime + 1000)) {
mLastTime = now;
fps = ifps;
ifps = 0;
}
}
} finally {
if (c != null) {
mHolder.unlockCanvasAndPost(c);
}
}
}
}
}
The activity that I want to start is called LoseActivity.class. Thank you in advance for any and all help. If anybody needs any further code/explanations, I will be more than happy to post it.
Use runOnUiThread for starting Activity from Thread as:
Change your main Activity as:
public class MainActivity extends Activity {
Button btn_startGame;
Activity activity;
GameView mGameView;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
activity = this;
setContentView(R.layout.main);
btn_startGame = (Button) findViewById(R.id.btnStartGame);
btn_startGame.setOnClickListener(new OnClickListener() {
//#Override
public void onClick(View v) {
mGameView = new GameView(activity,MainActivity.this);
setContentView(mGameView);
mGameView.mThread.doStart();
}
});
}
///your code.....
Change your GameView class as:
public class GameView extends SurfaceView implements Callback {
Context mContext;
Activity contextx;
GameThread mThread;
public GameView(Context context,Activity contextx) {
super(context);
this.mContext = context;
this.contextx=contextx;
getHolder().addCallback(this);
mThread = new GameThread(getHolder(), mContext, new Handler() {
#Override
public void handleMessage(Message m) {
// Use for pushing back messages.
}
});
setFocusable(true);
}
//your code here..........
#Override
public void run() {
// check if condition here
if(lost){
contextx.runOnUiThread(new Runnable() {
#Override
public void run() {
//start Activity here
Intent intent = new Intent(contextx, LoseActivity.class);
contextx.startActivity(intent);
}
});
}
else{
//your code here.........
I am setting up to use the Loader pattern and had issues using the cursor approach, so I have refactored my code because my tables do not use _id as the primary key because of the use of association tables and I setup my code to use the same basic structure as the LoaderCustomSupport.java example from the android developer site. All of the code works without errors and I can see that I have the proper data back and ready for the ListFragment to display but after the onLoadFinished call back completes the setData on the adapter the getView is never called. My getView looks like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
PhoneNumberListHolder holder;
if (row == null) {
row = mInflater.inflate(R.layout.phonenumber_row, parent, false);
holder=new PhoneNumberListHolder(row);
row.setTag(holder);
} else {
holder = (PhoneNumberListHolder)row.getTag();
}
holder.populateForm(this.phoneNumbers.get(position));
return row;
}
I am trying to use the holder pattern, but I am thinking that maybe it is part of my issue. Any ideas where I might be going wrong?
Here is the loader code (Like I said I followed the Google example for my first run changing what I thought I would need)
The Abstract Loader for my Class
/*
* Custom version of CommonsWare, LLC, AbstractCursorLoader
*
*/
package myApp.service.data;
import java.util.List;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
abstract public class AbstractPhoneNumberLoader extends AsyncTaskLoader<List<PhoneNumber>> {
abstract protected List<PhoneNumber> buildPhoneNumber();
List<PhoneNumber> lastPhoneNumber=null;
public AbstractPhoneNumberLoader(Context context) {
super(context);
}
#Override
public List<PhoneNumber> loadInBackground() {
List<PhoneNumber> data=buildPhoneNumber();
if (data!=null) {
// Make sure we fill the person
data.size();
}
return (data);
}
/**
* This will run on the UI thread, routing the results from the
* background to the consumer of the Person object
* (e.g., a PhoneNumberListAdapter).
*/
#Override
public void deliverResult(List<PhoneNumber> data) {
if (isReset()) {
// An async query attempted a call while the loader is stopped
if (data!=null) {
data.clear();
data=null;
//not sure the best option here since we cannot close the List object
}
return;
}
List<PhoneNumber> oldPhoneNumber=lastPhoneNumber;
lastPhoneNumber=data;
if (isStarted()) {
super.deliverResult(data);
}
if (oldPhoneNumber!=null && oldPhoneNumber!=data && oldPhoneNumber.isEmpty()) {
oldPhoneNumber.clear();
oldPhoneNumber=null;
}
}
/**
* Start an asynchronous load of the requested data.
* When the result is ready the callbacks will be called
* on the UI thread. If a previous load has completed
* and is still valid the result may be passed back to the
* caller immediately.
*
* Note: Must be called from the UI thread
*/
#Override
protected void onStartLoading() {
if (lastPhoneNumber!=null) {
deliverResult(lastPhoneNumber);
}
if (takeContentChanged() || lastPhoneNumber==null) {
forceLoad();
}
}
/**
* Must be called from the UI thread, triggered by
* a call to stopLoading().
*/
#Override
protected void onStopLoading() {
// Attempt to cancel the current load task
cancelLoad();
}
/**
* Must be called from the UI thread, triggered by a
* call to cancel(). Here, we make sure our Person
* is null, if it still exists and is not already empty.
*/
#Override
public void onCanceled(List<PhoneNumber> data) {
if (data!=null && !data.isEmpty()) {
data.clear();
}
}
/**
* Must be called from the UI thread, triggered by a
* call to reset(). Here, we make sure our Person
* is empty, if it still exists and is not already empty.
*/
#Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
if (lastPhoneNumber!=null && !lastPhoneNumber.isEmpty()) {
lastPhoneNumber.clear();
}
lastPhoneNumber=null;
}
}
The Data Loader
/*
* Custom version of CommonsWare, LLC, SQLiteCursorLoader
*
*/
package myApp.service.data;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.List;
import myApp.service.data.ActorDbAdapter;
import android.content.Context;
public class PhoneNumberDataLoader extends AbstractPhoneNumberLoader {
ActorDbAdapter db=null;
protected final String actorId;
protected final String _PHONENUMBERID = "PhoneNumberId";
protected SimpleDateFormat dateFormat = new SimpleDateFormat("dd/mm/yyyy");
/**
* Constructor - takes the context to allow the database to be
* opened/created
*
* #param ctx the Context within which to work
*/
public PhoneNumberDataLoader(Context ctx, String actorId) {
super(ctx);
this.actorId = actorId;
getHelper(ctx);
}
// Get a database connection
private void getHelper(Context ctx) {
if (db==null) {
db=new ActorDbAdapter(ctx);
}
db.open();
}
// Loader Methods
/**
* Runs on a worker thread and performs the actual
* database query to retrieve the PhoneNumber List.
*/
#Override
protected List<PhoneNumber> buildPhoneNumber() {
return(db.readPhoneById(actorId));
}
/**
* Writes a semi-user-readable roster of contents to
* supplied output.
*/
#Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
writer.print(prefix);
writer.print("actorId=");
writer.println(actorId);
}
public void insert(PhoneNumber data, String actorId) {
new InsertTask(this).execute(db, data, actorId);
}
// Saved for Later, get Reads and Writes working first
// public void update(PhoneNumber data, String actorId, String whereClause, String[] whereArgs) {
// new UpdateTask(this).execute(db, data, actorId, whereClause, whereArgs);
// }
//
// public void delete(String actorId, String phoneNumberId, String whereClause, String[] whereArgs) {
// new DeleteTask(this).execute(db, actorId, phoneNumberId, whereClause, whereArgs);
// }
public void execSQL(String actorId) {
new ExecSQLTask(this).execute(db, actorId);
}
private class InsertTask extends ContentChangingTask<Object, Void, Void> {
InsertTask(PhoneNumberDataLoader loader) {
super(loader);
}
#Override
protected Void doInBackground(Object... params) {
ActorDbAdapter db=(ActorDbAdapter)params[0];
PhoneNumber data=(PhoneNumber)params[1];
int actorId=Integer.parseInt((String)params[2]);
db.createPhoneNumber(data, actorId);
return(null);
}
}
// Saved for Later, get Reads and Writes working first
// private class UpdateTask extends
// ContentChangingTask<Object, Void, Void> {
// UpdateTask(PhoneNumberDataLoader loader) {
// super(loader);
// }
//
// #Override
// protected Void doInBackground(Object... params) {
// ActorDbAdapter db=(ActorDbAdapter)params[0];
// String table=(String)params[1];
// int actorId=Integer.parseInt((String)params[2]);
// String where=(String)params[3];
// String[] whereParams=(String[])params[4];
//
// db.updatePhoneNumber(table, values, where, whereParams);
//
// return(null);
// }
// }
//
// private class DeleteTask extends
// ContentChangingTask<Object, Void, Void> {
// DeleteTask(PhoneNumberDataLoader loader) {
// super(loader);
// }
//
// #Override
// protected Void doInBackground(Object... params) {
// ActorDbAdapter db=(ActorDbAdapter)params[0];
// int actorId=Integer.parseInt((String)params[1]);
// int phoneNumberId=Integer.parseInt((String)params[2]);
// String where=(String)params[3];
// String[] whereParams=(String[])params[3];
//
// db.deletePhoneNumber(table, where, whereParams);
//
// return(null);
// }
// }
private class ExecSQLTask extends
ContentChangingTask<Object, Void, Void> {
ExecSQLTask(PhoneNumberDataLoader loader) {
super(loader);
}
#Override
protected Void doInBackground(Object... params) {
ActorDbAdapter db=(ActorDbAdapter)params[0];
String actorId=(String)params[1];
db.readPhoneById(actorId);
return(null);
}
}
}
Here is my full ListAdapter
package myApp.planner.utilities;
import java.util.ArrayList;
import java.util.List;
import myApp.planner.R;
import myApp.planner.codes.PhoneOrAddressTypeCode;
import myApp.service.data.PhoneNumber;
import myApp.service.data.PhoneNumberListData;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.TextView;
public class PhoneNumberListAdapter extends ArrayAdapter<PhoneNumber> {
private List<PhoneNumber> phoneNumbers;
private final LayoutInflater mInflater;
private Activity activity;
public PhoneNumberListAdapter(Activity a, int textViewResourceId) {
super(a, textViewResourceId);
activity = a;
mInflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setData(List<PhoneNumber> data) {
if (this.phoneNumbers==null) {
this.phoneNumbers = new ArrayList<PhoneNumber>();
}
this.phoneNumbers.clear();
if (data != null) {
for (PhoneNumber phoneNumber : data) {
this.phoneNumbers.add(phoneNumber);
}
}
}
public static class PhoneNumberListHolder {
private TextView actorid=null;
private TextView phonenumberid=null;
private TextView phonetype=null;
private TextView phonenumber=null;
private CheckBox isprimary=null;
PhoneNumberListHolder(View row) {
actorid=(TextView)row.findViewById(R.id.actorid);
phonenumberid=(TextView)row.findViewById(R.id.phonenumberid);
phonetype=(TextView)row.findViewById(R.id.txtphonetype);
phonenumber=(TextView)row.findViewById(R.id.txtphonenumber);
isprimary=(CheckBox)row.findViewById(R.id.isprimary);
}
//void populateForm(ArrayList<PhoneNumberListData> c, int position) {
void populateForm(PhoneNumber data) {
//PhoneNumberListData data = c.get(position);
// Attempt to add the Actor ID
if (actorid != null){
actorid.setText(data.getActor().get(0).getActorId()==0 ? "0": Integer.toString(data.getActor().get(0).getActorId()));
}
// Attempt to add the Phone Number Item ID
if (phonenumberid != null){
phonenumberid.setText(data.getPhoneNumberId()==0 ? "0": Integer.toString(data.getPhoneNumberId()));
}
// Attempt to add the Phone Number Type
if (phonetype != null){
phonetype.setText(data.getPhoneType()==null ? "": PhoneOrAddressTypeCode.valueOf(data.getPhoneType()).toString());
}
// Attempt to add the Phone Number
if (phonenumber != null){
phonenumber.setText(data.getPhoneNumber1()==null ? "": data.getPhoneNumber1());
}
// Attempt to add the is Primary Flag
if (isprimary != null){
isprimary.setChecked(data.getIsPrimary()==0 ? Boolean.FALSE: Boolean.TRUE);
}
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
PhoneNumberListHolder holder;
if (row == null) {
row = mInflater.inflate(R.layout.phonenumber_row, parent, false);
holder=new PhoneNumberListHolder(row);
row.setTag(holder);
} else {
holder = (PhoneNumberListHolder)row.getTag();
}
holder.populateForm(this.phoneNumbers.get(position));
return row;
}
}
The ListFragment:
package myApp.planner;
import java.util.ArrayList;
import java.util.List;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SearchViewCompat;
import android.support.v4.widget.SearchViewCompat.OnQueryTextListenerCompat;
import android.text.TextUtils;
import android.content.Intent;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.database.Cursor;
import android.view.ContextMenu;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ListView;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import myApp.planner.R;
import myApp.planner.utilities.PhoneNumberListAdapter;
import myApp.service.data.PhoneNumber;
import myApp.service.data.PhoneNumberDataLoader;
public class ActorPhoneNumberListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List<PhoneNumber>> {
public final static String ID_EXTRA="myapp.planner.actorid";
protected final static String TAG = "ActorPhoneNumberListFragment";
private static final int ADD_ID=Menu.FIRST + 1;
private static final int DELETE_ID=Menu.FIRST + 3;
private PhoneNumberListAdapter mAdapter=null;
private PhoneNumberDataLoader loader=null;
private String mCurFilter;
private String actorId = "0";
//private SharedPreferences prefs=null;
OnActorPhoneNumberListListener listener=null;
OnQueryTextListenerCompat mOnQueryTextListenerCompat;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
this.actorId = getActivity().getIntent().getExtras().getString(ID_EXTRA).toString();
}
#Override
public void onResume() {
super.onResume();
Bundle args=getArguments();
if (args!=null) {
loadPhoneNumbers(args.getString(ID_EXTRA));
}
// init Empty Test for no Phone numbers Found
// Add the menu options that we need to manage the list
setHasOptionsMenu(true);
// Hookup the dbAdapter and create a blank adapter
initList();
}
#Override
public void onPause() {
super.onPause();
}
#Override
public void onListItemClick(ListView list, View view, int position, long id) {
if (listener!=null) {
//We will actually want the PhoneNumber Id here for the popup edit screen
String mId = actorId;
listener.onActorPhoneNumberListSelected(mId);
}
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.phonenumber_opton, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId()==R.id.addNewPhone) {
//add();
return(true);
} else if (item.getItemId()==R.id.help) {
startActivity(new Intent(getActivity(), HelpPage.class));
return(true);
} else if (item.getItemId()==R.id.phoneRefresh) {
//startActivity(new Intent(getActivity(), ActorPhoneNumberListFragment.class));
//We may just need to refresh the loader
return(true);
} else
return(super.onOptionsItemSelected(item));
}
public void setOnActorPhoneNumberListListener(OnActorPhoneNumberListListener listener) {
this.listener=listener;
}
public void loadPhoneNumbers(String actorId) {
this.actorId=actorId;
}
private void initList() {
mAdapter=new PhoneNumberListAdapter(getActivity(), R.layout.phonenumber_row);
setListAdapter(mAdapter);
// Start out with a progress indicator.
setListShown(false);
getActivity().getSupportLoaderManager().initLoader(0, null, this);
}
public interface OnActorPhoneNumberListListener {
void onActorPhoneNumberListSelected(String actorId);
}
#Override
public Loader<List<PhoneNumber>> onCreateLoader(int loaderId, Bundle args) {
loader= new PhoneNumberDataLoader(getActivity(), actorId);
return(loader);
}
#Override
public void onLoadFinished(Loader<List<PhoneNumber>> loader, List<PhoneNumber> data) {
// Now give the data to the adapter
mAdapter.setData(data);
mAdapter.notifyDataSetChanged();
//setListAdapter(mAdapter);
// Show the list
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
#Override
public void onLoaderReset(Loader<List<PhoneNumber>> arg0) {
// TODO Auto-generated method stub
mAdapter.setData(null);
}
}