Call non-static method of another class - java

I have two classes pertaining to this issue: AddA and TopicSpinner.
In the AddA class, I am setting an on touch listener for a spinner. When the spinner is selected, I want to call the method loadSpinnerData() of the TopicSpinner class. So I want to call a non-static method of the TopicSpinner class in the AddA class.
I know that starting the TopicSpinner class from the AddA class works with an Intent call so the loadSpinnerData() method does work properly. I tried several approaches in the listener as you see below. However, I cannot get the method to be called without a null pointer or "non-static method cannot be referenced from static method".
Any suggestions of how to call the method loadSpinnerData() of the TopicSpinner class in the listener method of the AddA class?
Here is the current exception:
java.lang.NullPointerException
at android.content.ContextWrapper.getApplicationContext(ContextWrapper.java:109)
at TopicSpinner.loadSpinnerData(TopicSpinner.java:56)
at AddA$2.onTouch(AddAlerts.java:117)
at android.view.View.dispatchTouchEvent(View.java:7241)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2168)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1903)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1953)
at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1405)
at android.app.Activity.dispatchTouchEvent(Activity.java:2410)
at android.support.v7.internal.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:59)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1901)
at android.view.View.dispatchPointerEvent(View.java:7426)
at android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:3220)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:3165)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:4292)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:4271)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:4363)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:179)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:125)
at android.os.Looper.loop(Looper.java:124)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)

Instead of adding the listener in your AddA class, just add it in your TopicSpinner class. You can do it in your onCreate method. Then you can directly reference your loadSpinnerData method.
public class TopicSpinner extends AddAlerts implements OnItemSelectedListener {
// Spinner element
Spinner spinner;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_alerts);
//Spinner element
spinner = (Spinner) findViewById(R.id.spinner);
//Spinner click listener
spinner.setOnItemSelectedListener(this);
//ADD LISTENER HERE
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
loadSpinnerData();
}
return false;
}
});
//Loading spinner data from database
loadSpinnerData();
}
/**
* Function to load the spinner data from SQLite database
**/
public void loadSpinnerData() {
// database handler
DatabaseHelper db = DatabaseHelper.getInstance(getApplicationContext());
Cursor topicCursor = db.getAllTopics();
Log.v("topicCursor", topicCursor.toString());
db.close();
String str;
ArrayList<String> labels = new ArrayList<String>();
if (topicCursor.moveToFirst()) {
do {
str = topicCursor.getString(topicCursor.getColumnIndex("topic_name"));
Log.v("str", str);
labels.add(str);
Log.v("labels", labels.toString());
} while (topicCursor.moveToNext());
}
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, labels);
//Drop down layout style - list view with radio button
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Log.v("dataAdapter", dataAdapter.toString());
//Attaching data adapter to spinner
spinner.setAdapter(dataAdapter);
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// On selecting a spinner item
String label = parent.getItemAtPosition(position).toString();
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
}
}

By your question, I call tell the confusion between a "Class" and an "Object". By calling a static method, your are calling an action on no particular instance of a TopicSpinner. By calling loadSpinnerData() as it is (not static) you are saying to a precise TopicSpinner, "do the job of loadSpinnerData()". See here to learn more.
I can see in TopicSpinner that you have a field "spinner" that is onwed by "an instance" of TopicSpinner. For example, you could have 2 TopicSpinners having a different Spinner each. The method loadSpinnerData() will actually modify that Spinner. In other words, loadSpinnerData() needs to be called via an instance (object) not via a static call, because it needs to know what Spinner it's actually going to play with.
Now why do you get a Nullpointerexception if you call it correctly? If you call new TopicSpinner() manually, then it means, run the loadSpinnerData() on a new instance of the TopicSpinner that I just created. I have a feeling your topic spinner already exists. You might be able to access it by drilling down in the objects View and MotionEvent. Try to debug and look for the TopicSpinner instance. Then, you can call loadSpinnerData() on that instance.
Hope I didn't confuse you too mch, but you really need to differenciate a class and an instance of a class (object).

Your issue is that "spinner" is not set (is null) in the following line in loadSpinnerData():
spinner.setAdapter(dataAdapter);
When you launch the class with an Intent, it executes onCreate, which properly initializes the "spinner" variable. You could declare "spinner" as an instance variable in AddA, then send it as a parameter to the constructor for TopicSpinner, where you capture and save the reference. Something like this:
public AddA extends Activity {
private Spinner spinner;
private Context context;
//In onCreate or wherever
spinner = (Spinner) findViewById(R.id.spinner);
context = this;
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
TopicSpinner ts = new TopicSpinner(spinner, context);
ts.loadSpinnerData();
}
//etc
Now in TopicSpinner add a constructor:
public class TopicSpinner extends AddA implements OnItemSelectedListener {
// Spinner element
private Spinner spinner;
private Context context;
public TopicSpinner(Spinner spinner, Context context){
this.spinner = spinner;
this.context = context;
}
//etc
In your TopicSpinner class replace getContext() with context.
The rest of your code remains untouched.

You could try passing in the spinner or context in as a parameter?
So copy and paste your load spinner data and sub out with the parameterized spinner and context:
public Spinner loadSpinnerData(Context context, Spinner spinner) {
// database handler
DatabaseHelper db = DatabaseHelper.getInstance(context);
Cursor topicCursor = db.getAllTopics();
Log.v("topicCursor", topicCursor.toString());
db.close();
String str;
ArrayList<String> labels = new ArrayList<String>();
if (topicCursor.moveToFirst()) {
do {
str = topicCursor.getString(topicCursor.getColumnIndex("topic_name"));
Log.v("str", str);
labels.add(str);
Log.v("labels", labels.toString());
} while (topicCursor.moveToNext());
}
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, labels);
//Drop down layout style - list view with radio button
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Log.v("dataAdapter", dataAdapter.toString());
//Attaching data adapter to spinner
spinner.setAdapter(dataAdapter);
return spinner;
}
And then reassign the spinner you passed in to be the new one that comes out of this method. It will hold its previous references as well because we are not assigning it in this method

Old Answer, (left because this 'was' true at the time, but the above code/info has changed)
To start with your first mistake is not storing the 'new' TopicSpinner. That is why you can't call 'member methods(might be wrong name, but 'non-static' methods)' of the class.
final Spinner spinner = (Spinner) findViewById(R.id.spinner);
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// Intent i = new Intent(AddA.this, TopicSpinner.class);
//startActivity(i);
//TopicSpinner ts = new TopicSpinner();
//ts.loadSpinnerData(); //null
// the problem is that you aren't saving the reference to the
// newly created TopicSpinner Object.
// Storing that reference here allows you to call
// non-static methods.
TopicSpinner myTopicSpinner = new TopicSpinner();
// Perhaps this can be above if it follows the builder pattern...
// but if not, then you need to separate these calls.
myTopicSpinner.loadSpinnerData(); // loadSpinnerData you have listed returns 'void' so it doesn't follow builder pattern.
// if myTopicSpinner is null here then you have bigger issues in THAT class.
// Now you can call non-static methods on the OBJECT.
// Since you 'have' an object now.
myTopicSpinner.loadSpinnerData(); //non-static method
}
return false;
}
});

Calling your Activity classes by the names of other classes is seriously confusing people.
AddA is an Activity.
TopicSpinner is also an Activity since you extends AddA.
Calling new TopicSpinner() creates an Activity object, but does not inflate it, so calling setContentView and findViewById are pointless inside of TopicSpinner. It might even return null because the view cannot be found.
As is, you have two options and both get rid of the extends AddA.
Option 1: Just load the data in AddA
Option 2: Create a separate class that loads the data that is not an Activity. You are getting all confused with onCreate and setContentView and findViewById. All you need is the Context and Spinner.
public class AddA extends Activity implements AdapterView.OnItemSelectedListener {
private Spinner spinner;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_a);
spinner = (Spinner) findViewById(R.id.spinner);
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
/** Option 1 **/
loadSpinnerData();
/** Option 2 **/
TopicLoader loader = new TopicLoader(getApplicationContext());
loader.loadSpinner(spinner);
}
return false;
}
});
}
private void loadSpinnerData() {
Context ctx = getApplicationContext();
// TODO: Implement Option 1 here
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// On selecting a spinner item
String label = parent.getItemAtPosition(position).toString();
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
}
public class TopicLoader {
private Context mCtx;
public TopicLoader(Context ctx) {
this.mCtx = ctx;
}
public void loadSpinner(Spinner spinner) {
// Context ctx = this.mCtx;
// TODO: Implement Option 2 here
}
}

DatabaseHelper db =
DatabaseHelper.getInstance(getApplicationContext()); return null.
If this is the case, then your DatabaseHelper class is where your issues
actually is.
Here is a question with an (almost) complete answer that will show you what to do (you will need to impl. your own rawQuery(), that they don't do in that link)
Looking for Cause of Null Pointer Exception
There are a couple things that you can try. (Hard to tell without seeing source for DatabaseHelper)
1) DatabaseHelper db = new DatabaseHelper(getApplicationContext()); then db.open() right after it. (if you follow the pattern that I show below). Use normal constructor instead of 'getInstance()'. I haven't seen DBHelper classes being singletons or something else that would require 'getInstance()' before. A SQLite DB should be able to handle multiple-concurrent readable DB accesses EASILY (writes have timing issues, but that is 'app logic' not the SQLite DB itself).
2) Start logging within your DatabaseHelper class and see where it fails internally.
Here is (part of) a sample DBHelper class I wrote a long time ago.
You shouldn't be getting a 'null' when you try to instantiate yours. You should make sure that class is operating properly. If you don't have one you should have an internal SQLiteOpenHelper class (very helpful).
// Database open/upgrade helper
private myDbHelper dbHelper; // this is inside my DBAdapter class
// ...
/**
* constructor that accepts the context to be associated with
*
* #param _context
*/
public DataDBAdaptor(Context _context) {
Log.d(LOG_TAG, "MyDBAdapter constructor");
context = _context;
dbHelper = new myDbHelper(context, DATABASE_NAME, null,
DATABASE_VERSION);
}
/**
* open the DB, and write/read access or
* just read access if that is all that is possible.
*
* #return this DataDBAdaptor
* #throws SQLException
*/
public MoocDataDBAdaptor open() throws SQLException {
Log.d(LOG_TAG, "open()");
try {
db = dbHelper.getWritableDatabase();
} catch (SQLException ex) {
db = dbHelper.getReadableDatabase();
}
return this;
}
Here is the Helper class
/**
* DB Helper Class.
*
* #author mawalker
*
*/
private static class myDbHelper extends SQLiteOpenHelper {
public myDbHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
}
#Override
public void onCreate(SQLiteDatabase db) {
Log.d(LOG_TAG, "DATABASE_CREATE: version: " + DATABASE_VERSION);
// ST:createTable:start
db.execSQL(DATABASE_CREATE_STORY);
db.execSQL(DATABASE_CREATE_TAGS);
// ST:createTable:finish
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Log version upgrade.
Log.w(LOG_TAG + "DBHelper", "Upgrading from version " + oldVersion
+ " to " + newVersion + ", which will destroy all old data");
// **** Upgrade DB ****
// TODO: migrate data?? from old DB to new DB
// drop old DB
// ST:dropTableIfExists:start
db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE_STORY);
db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE_TAGS);
// ST:dropTableIfExists:finish
// Create a new one.
onCreate(db);
}
}

Related

Custom adapter's asynchronous behaviour

I have created my own custom adapter class in my android app and I am calling it from one of my activity. I am adding some elements to the view from the adapter class and I need to access those variables from my activity class.
Now, ideally I would expect it to fill the view and then execute the further code in my activity class, but adapter class is taking some time to populate the view and in the meanwhile further code in my activity class is getting executed where no such elements have been added yet.
How do I handle this situation? I come from a js background. Do we have something like promises in java?
According to the answers I have my changed my code to this:
public class HomeActivity extends Activity {
GridView grid;
String text[] = {"Calendar","Uber","Weather","News","Youtube","Clock","Email","Maps","Twitter","Facebook"};
String list_app_name[] = {"calendar","uber","weather","news","youtube","clock","email","maps","twitter","facebook"};
String id_button[] = {"button_calendar","button_uber","button_weather","button_news","button_youtube","button_clock","button_email","button_maps","button_twitter","button_facebook"};
int image[] = {R.drawable.social_icons1,R.drawable.social_icons2,R.drawable.social_icons3,R.drawable.social_icons4,
R.drawable.social_icons5,R.drawable.social_icons6, R.drawable.social_icons7,R.drawable.social_icons8,
R.drawable.social_icons9,R.drawable.social_icons10};
private DrawerLayout mDrawerLayout;
private ActionBarDrawerToggle mDrawerToggle;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_home);
//setting up the adapter for gridView
grid = (GridView)findViewById(R.id.simpleGrid);
ImageAdapter ia = new ImageAdapter(this,image,text,id_button);
grid.setAdapter(ia);
ia.notifyDataSetChanged();
try {
initStateOfApps();
} catch (JSONException e) {
e.printStackTrace();
}
}
public void initStateOfApps() throws JSONException {
Log.d("here","here");
ArrayList<String> list = getEnabledApps();
Log.d("apps",list.toString());
for(int i=0;i<list.size();i++) {
String app_name = list.get(i);
ToggleButton button=null;
if(app_name.equals("calendar")) {
button = (ToggleButton)findViewById(R.id.button_calendar);
button.setChecked(true);
}
}
}
}
So what is happening is that I am creating some toggle buttons that are getting populated in the ImageAdapter class that I wrote.
Once the ImageAdapter is called, I call the notifydatasetchanged() on the adapter in order to update the view.
What I am doing inside the adapter is giving each of the toggle buttons some custom ID I wrote in res/values/ids.xml.
After using setId on each of the toggle buttons, I try using that ID in my activity class but it gives me nullPointerException in the initStateOfApps() where I am trying to change the state of button.
So even after using the notifyDataSetChanged it is still behaving the same.
ImageAdapter.java
public class ImageAdapter extends BaseAdapter {
private Context context;
private final int item_image[];
private final String item_text[];
private final String button_id[];
public ImageAdapter(Context context, int item_image[], String[] item_text,String[] button) {
this.context = context;
this.item_image = item_image;
this.item_text = item_text;
this.button_id = button;
}
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View gridView;
if (convertView == null) {
gridView = new View(context);
// get layout from custom_gridview.xml
gridView = inflater.inflate(R.layout.item, null);
// set value into imageview
final ImageView image = (ImageView) gridView.findViewById(R.id.item_image);
image.setImageResource(item_image[position]);
// set value into textview
TextView text = (TextView) gridView.findViewById(R.id.item_text);
text.setText(item_text[position]);
final ToggleButton button_ = (ToggleButton) gridView.findViewById(R.id.item_button);
if(position==0) {
button_.setId(R.id.button_calendar);
image.setId(R.id.image_calendar);
}
button_.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener()
{
#Override
public void onCheckedChanged(CompoundButton toggleButton, boolean isChecked)
{
if(context.getResources().getResourceEntryName(toggleButton.getId()).equals("button_calendar")) {
if(isChecked) {
try {
setStateOfApp("calendar","true");
} catch (JSONException e) {
e.printStackTrace();
}
Intent intent = new Intent(context, GoogleApp.class);
((Activity) context).startActivityForResult(intent,10);
} else {
try {
setStateOfApp("calendar","false");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
}
} else {
gridView = (View) convertView;
}
return gridView;
}
}
You are trying to access View which is not a part of Activity's content view. So you can't access that view directly.
button = (ToggleButton)findViewById(R.id.button_calendar); // will return null
This ToggleButton will be null because findViewById will fail to find out ToggleButton in current content view because that view is present in your Adapter not in content view.
And you are getting nullpointerException because you are trying to access property on null view.
button.setChecked(true); // This button is null
In java we have <Future>, but I don't think it's what you're looking for.
The adapter (extending BaseAdaper) behaviour lets you create the adapter and, even in a second moment, change underlying data via getAdapter().setData() or whatever method you choose to add.
From this perspective, the adapter is a "stupid" component acting as A View containers, you should retrieve data elsewhere (CursorAdapter is different).
So, in your Activity, fill the adapter with needed data and, when finished, call adapter.notifyDatasetChanged(). This will inform the adapter that its own data has changed and it must refresh views
Yes, ideally, the population of the adapter should be coming from the outside. The adapter should really just take in a list of data and map that data to the views. For example, some method or task in the Activity could produce a list of data (probably asynchronously...since you mentioned it) that you then pass into the adapter and then you can notifyDataSetChanged() if you need to.
I can't see your code, but if for some reason the data is truly required to be populated from inside the adapter, you could use an event bus and subscribe to it in the Activity. I would recommend going with the first option, but here are some links if you choose to use an event bus:
https://github.com/greenrobot/EventBus
http://square.github.io/otto/
As per my understanding with your question
You are not properly managed the adapter data in your activity.
If any of the data or code interlinked with your adapter data or values
Then you can start those code after you retrieve the values or data and update the view in your activity.
Please note that use Viewholders in adapter to avoid slow populating and scrolling in listviews.
Viewholders will smooth your process.
I personally suggest you that
Please go with Recyclerview and RecyclerViewAdapter.
So many Android developers are using it.
If you have background tasks in adapter you can prefer to use RX Java or EventBus
If you provide the code
It's better for us to suggest exact solution

Storing context in an adapter when it seems unnecessary to do so

I was looking at examples on how to use an adapter to handle live query here: ToDoLite-Android LiveQueryAdapter, and I have a question regarding this part:
private LiveQuery query;
private QueryEnumerator enumerator;
private Context context;
public LiveQueryAdapter(Context context, LiveQuery query) {
this.context = context;
this.query = query;
query.addChangeListener(new LiveQuery.ChangeListener() {
#Override
public void changed(final LiveQuery.ChangeEvent event) {
((Activity) LiveQueryAdapter.this.context).runOnUiThread(new Runnable() {
#Override
public void run() {
enumerator = event.getRows();
notifyDataSetChanged();
}
});
}
});
query.start();
}
In several activities, he/she used adapters that extend LiveQueryAdapter. The adapters were initialized by passing in an activity and a live query.
Examples here:
// 'this' are all activities, I think
mAdapter = new ListAdapter(this, query.toLiveQuery());
mAdapter = new UserAdapter(this, getQuery().toLiveQuery());
mAdapter = new TaskAdapter(this, query.toLiveQuery());
So here's my question: Why do we need to store that context in the contructor?
Does it have something to do with runOnUiThread? Why not just do this:
((Activity)context).runOnUiThread(...
I don't see the stored context variable used anywhere else but in that one line, so why bother saving it?
I know the context can probably be used in getView(), but his/her code uses parent.getContext() instead. Also, in my code I am using RecyclerView instead of ListView so I don't even have getView() method I don't think.
The context allows the adapter to reference the activity and application state. The Adapter needs context update the activity and access any UI components

Spinner in Custom Function for Android with java

I am confused about calling the spinner widget through a custom function. I am create an app in which I use spinner 20-35 times a spinner widget in single layout or activity. So for this i want to avoid the spinner code repetition again and again. i am creating a method for this i add the items to the spinner but i want to pass item value on select to other activity which bind to that class
Here is my code
Spin_tester.class
public class Spin_tester {
public String result;
public Context ctx;
public Spin_tester(Spinner spinner, final ArrayList<String> arraylist, final Context ctx , final String value) {
this.ctx= ctx;
ArrayAdapter<String> adpts =
new ArrayAdapter<String>(ctx, android.R.layout.simple_dropdown_item_1line,arraylist);
spinner.setAdapter(adpts);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
result = arraylist.get(position);
value = result ; // This is not working
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
}
Test_Activity.class
public class Test_Activity extends AppCompatActivity {
ArrayList<String> data_list = new ArrayList<>();
Spinner spins;
String value;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
spins = (Spinner)findViewById(R.id.spinner);
data_list.add("1");
data_list.add("2");
data_list.add("3");
data_list.add("4");
Spin_tester asd = new Spin_tester(spins,data_list,this,value);
TextView txt = (TextView)findViewById(R.id.textView17);
}
}
Please help
Thanks in advance
Java is a pass-by-value language, not pass-by-reference. What this means is that setting value in setOnItemSelectedListener will only change value within that method — the result won't be passed back anywhere.
I see that you've place the result in result. That is where the calling program will find the answer.
Remove all instances of value from Spin_tester and Test_Activity and then have your main activity get the result from asd.result
I'm going to get a little meta at this point: I've only answered the question you actually asked, but this code is wrong on so many levels that you're never going to get it to work. I strongly suggest you work your way through the examples and tutorials in the documentation before you try to proceed any further.

Android ListFragment data not updating via SimpleCursorAdapter / LoaderManager / notifyChange()

I have a ListFragment that shows the names of all the shopping lists stored in my database table.
The problem is that when I add a new shopping list row to the table, the ListFragment on the UI is not automatically updated. (The change can only be seen if I close and restart the app.)
Firstly, this is the code that is executed in my DbContentProvider class when I add a shopping list:
`
// Insert the values into the table
id = db.insert(SHOPPING_LISTS_META_TABLE, null, values);
if (id > -1) {
// Construct and return the URI of the newly inserted row.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI_SHOPPING_LISTS_META, id);
// Notify any observers of the change in the data set.
Log.d(LOG_TAG, "................. notifyChange(\"" + insertedId + "\", null)");
getContext().getContentResolver().notifyChange(insertedId, null);
Log.d(LOG_TAG, "................. notifyChange() done");
return insertedId;
}
else {
return null;
}
`
...and here is the LogCat output for it...
10-28 12:29:41.133: D/SQLiteOpenHelper(19401): ................. notifyChange("content://org.example.myapp.DbContentProvider/shopping_lists_meta/12", null)
10-28 12:29:41.143: D/SQLiteOpenHelper(19401): ................. notifyChange() done
10-28 12:29:41.153: D/HomeActivity(19401): Shopping list, "My Test Shopping List" created: content://org.example.myapp.DbContentProvider/shopping_lists_meta/12
10-28 12:29:41.183: D/AbsListView(19401): unregisterIRListener() is called
10-28 12:29:41.193: E/ViewRootImpl(19401): sendUserActionEvent() mView == null
10-28 12:29:41.503: D/AbsListView(19401): unregisterIRListener() is called
In LogCat, there is no output at all from my ListFragment class when I add the new shopping list row. Here is my ListFragment class...
`
public class ShoppingListNamesListFragment extends ListFragment implements LoaderManager.LoaderCallbacks {
private final static String LOG_TAG = ShoppingListNamesListFragment.class.getSimpleName();
// This is the Adapter being used to display the list's data
public static SimpleCursorAdapter mAdapter;
// These are the Contacts rows that we will retrieve
static final String[] PROJECTION = {DbContentProvider.KEY_ID,
DbContentProvider.KEY_SHOPPING_LIST_NAME,
DbContentProvider.KEY_IS_SHOPPING_LIST_SELECTED};
// This is the select criteria
static final String SELECTION = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(LOG_TAG, "................. onCreate()");
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(LOG_TAG, "................. onActivityCreated()");
// For the cursor adapter, specify which columns go into which views
String[] fromColumns = {DbContentProvider.KEY_SHOPPING_LIST_NAME};
int[] toViews = {android.R.id.text1}; // The TextView in simple_list_item_1
// Create an empty adapter we will use to display the loaded data.
// We pass null for the cursor, then update it in onLoadFinished()
mAdapter = new SimpleCursorAdapter(this.getActivity(),
android.R.layout.simple_list_item_1, null,
fromColumns, toViews, 0);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(LOG_TAG, "................. onCreateView()");
return super.onCreateView(inflater, container, savedInstanceState);
}
// Called when a new Loader needs to be created
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Log.d(LOG_TAG, "................. onCreateLoader()");
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), DbContentProvider.CONTENT_URI_SHOPPING_LISTS_META,
PROJECTION, SELECTION, null, null);
}
// Called when a previously created loader has finished loading
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Log.d(LOG_TAG, "................. onLoaderFinished()");
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
// Called when a previously created loader is reset, making the data unavailable
#Override
public void onLoaderReset(Loader<Cursor> loader) {
Log.d(LOG_TAG, "................. onLoaderReset()");
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
makeToast("shopping list clicked: " + position);
Log.d(LOG_TAG, "shopping list clicked: " + position);
}
private void makeToast(String msg) {
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
NB - In my DbContentProvider class, I have...
public static final Uri CONTENT_URI_SHOPPING_LISTS_META = Uri.parse("content://org.example.myapp.DbContentProvider/shopping_lists_meta");
public static final String SHOPPING_LISTS_META_TABLE = "shopping_lists_meta";
I have based my code on this Android ListFragment / LoaderManager example. And none of the similar questions to this that I have found offer a solution that fixes my problem.
I am fairly new to Android, so it could be a simple mistake I've made. But, essentially, it seems to me that when notifyChange() is called in my DbContentProvider class, my ListFragment is not being notified (or there is some other error in this area). Can anyone help?
After spending hours and hours on this, I created a TestListActivity that hooked up to the native Contacts content provider - and that all worked/updated as it should, so I knew the issue was probably in my own content provider that I'd written.
I found the answer here. Turns out I had not called setNotificationUri(ContentResolver cr, Uri uri) on the cursor returned by the query() method of my content provider. (I'm sure this was never mentioned in the Reto Mauer book I was working from...) :/
Anyway, all sorted now! :)

Pass String variables by reference to setOnItemSelectedListener of multiple Spinners

OK, I've read around and see that Java only passes by value, not by reference so I don't know how to accomplish this.
I've 6 Spinners in an Android Activity that are populated with different SQLite queries.
The code to populate each Spinner and set the OnItemSelectedListener is very similiar so I was hoping to refactor to one method and call it 6 times with each Spinner ID and Sqlite query.
How do I get the Spinner onItemSelectedListener to change the right instance member on each different Spinner?
public void fillSpinner(String spinner_name, final String field_name) {
// This finds the Spinner ID passed into the method with spinner_name
// from the Resources file. e.g. spinner1
int resID = getResources().getIdentifier(spinner_name, "id",
getPackageName());
Spinner s = (Spinner) findViewById(resID);
final Cursor cMonth;
// This gets the data to populate the spinner, e.g. if field_name was
// strength = SELECT _id, strength FROM cigars GROUP BY strength
cMonth = dbHelper.fetchSpinnerFilters(field_name);
startManagingCursor(cMonth);
String[] from = new String[] { field_name };
int[] to = new int[] { android.R.id.text1 };
SimpleCursorAdapter months = new SimpleCursorAdapter(this,
android.R.layout.simple_spinner_item, cMonth, from, to);
months.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s.setAdapter(months);
// This is setting the Spinner Item Selected Listener Callback, where
// all the problems happen
s.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
Cursor theCursor = (Cursor) parent.getSelectedItem();
// This is the problem area.
object_reference_to_clas_member_of_field_name = theCursor
.getString(theCursor.getColumnIndex(field_name));
}
public void onNothingSelected(AdapterView<?> parent) {
// showToast("Spinner1: unselected");
}
});
}
You call this method like this fillSpinner("spinner1","strength");.
It finds the spinner with id spinner1 and queries the database for the strength field. field_name, which is strength in this example had to be declared a final variable to be used in the onItemSelectedListener or I'd get the error Cannot refer to a non-final variable field_name inside an inner class defined in a different method.
But how do I get the onItemSelectedListener to change the value of a different instance member when each different Spinner is used? This is the all important line of code:
object_reference_to_clas_member_of_field_name = theCursor .getString(theCursor.getColumnIndex(field_name));
I can't use a final String as the variable will obviously change when the user selects a different value. I've read around a good bit and am stumped to a solution. I can just copy and paste this code 6 times and forget about refactoring but I'd really like to know the elegant solution. Post a comment if you don't understand my question, I'm not sure if I explaned myself well.
You can do it, by passing additional class as parameter of fillSpinner method:
A. Create interface
public interface OnSpinnerValueSelected {
void onValueSelected(String selectedValue);
}
B. Change your method a bit:
public void fillSpinner(String spinner_name, final String field_name,
final OnSpinnerValueSelected valueChangeListener) {
// Prepare spinner
s.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
Cursor theCursor = (Cursor) parent.getSelectedItem();
valueChangeListener.onValueSelected(theCursor
.getString(theCursor.getColumnIndex(field_name)));
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
C. provide listener:
fillSpinner("spinner1","strength", new OnSpinnerValueSelected() {
public void onValueSelected(String selectedValue) {
yourObject.setField(selectedValue);
}
});
Refactor your listener to a new "class". Initialize with the right arguments/instances as required so that the repeated "code" is reusuable.
Right, this is how I managed it but I'm still open to new suggestions for an accepted answer and I also created a bounty.
I didn't create a new class like panzerschreck suggested so I'm posting this as a new answer to my own question. Bit of a hack but I just created an if..then..else statement in the listener to check what spinner was selected and then set a different instance member.
s.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
Cursor theCursor = (Cursor) parent.getSelectedItem();
if (field_name.equalsIgnoreCase("strength")) {
strength=theCursor.getString(theCursor.getColumnIndex(field_name));
} else if (field_name.equalsIgnoreCase("ring")) {
ring_gauge=theCursor.getString(theCursor.getColumnIndex(field_name));
} else if (field_name.equalsIgnoreCase("country")) {
country=theCursor.getString(theCursor.getColumnIndex(field_name));
} else if (field_name.equalsIgnoreCase("wrapper")) {
wrapper=theCursor.getString(theCursor.getColumnIndex(field_name));
} else if (field_name.equalsIgnoreCase("length")) {
length=theCursor.getString(theCursor.getColumnIndex(field_name));
} else if (field_name.equalsIgnoreCase("price")) {
price=theCursor.getString(theCursor.getColumnIndex(field_name));
}
// showToast(category);
}
public void onNothingSelected(AdapterView<?> parent) {
// showToast("Spinner2: unselected");
}
});
Here are the class members
private String strength,ring_gauge,country,wrapper,length,price;
Bit of hack but without Java allowing objects to be really passed by reference, it's all I could do.

Categories

Resources