I have an adapter used to display messages on the list view alike messages in chat application . I am able to display the content flawlessly once the activity is created , but when I go back and create activity again , adapter don't work as usual .
What I found in debugging is follows:
function receives() is called when message is received and update the
register , as I mentioned above there is no problem to display the
data in list view once the activity is created , but once I go back
and relauch the activity I am not able to display received messages .
Is there something I am missing in onResume() onPause or onStart() method with respect to custom adapter such as registering or decalring the custom adapter again? Thanks for help.
Following is the code of my activity class which uses custom adapter to display sent and received messages:
public class hotListener extends ListActivity {
private XMPPConnection connection;
private IBinder binder;
private Handler mHandler = new Handler();
private ArrayList<String> messages = new ArrayList<String>();
ArrayList<ChatMessage> messagex= new ArrayList<ChatMessage>();;
ChattingAdapter adaptex;
Intent mIntent ;
private ListView listview;
EditText sender_message ;
String msg;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listener);
//messagex.add(new ChatMessage("Hello", false));
adaptex = new ChattingAdapter(getApplicationContext(),messagex);
setListAdapter(adaptex);
Button send_button = (Button) findViewById(R.id.chat_send_message);
sender_message = (EditText) findViewById(R.id.chat_input);
send_button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
msg = sender_message.getText().toString();
sender_message.setText("");
if(!(msg.length()==0)){
messagex.add(new ChatMessage(msg, true));
//addNewMessage(new ChatMessage(msg, true));
adaptex.notifyDataSetChanged();
getListView().setSelection(messagex.size()-1);
}
}
});
if(!isMyServiceRunning()){
System.out.println("seems like service not running");
startService(new Intent(this,xService.class));
System.out.print(" now started ");
}
}
#Override
protected void onStart(){
super.onStart();
Boolean kuch = bindService(new Intent(this,xService.class), mConnection,Context.BIND_AUTO_CREATE);
//System.out.println(kuch);
System.out.println("bind done");
}
private void receives(XMPPConnection connection2) {
//ChatManager chatmanager = connection.getChatManager();
connection2.getChatManager().addChatListener(new ChatManagerListener() {
#Override
public void chatCreated(Chat arg0, boolean arg1) {
arg0.addMessageListener(new MessageListener() {
#Override
public void processMessage(Chat chat, Message message) {
final String from = message.getFrom();
final String body = message.getBody();
mHandler.post(new Runnable() {
ChatMessage kudi = new ChatMessage(body, false);
#Override
public void run() {
messagex.add(kudi);
adaptex.notifyDataSetChanged();
getListView().setSelection(messagex.size()-1);
Toast.makeText(hotListener.this,body,Toast.LENGTH_SHORT).show(); }
});
}
});
}
});
}
private boolean isMyServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for(RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)){
if(xService.class.getName().equals(service.service.getClassName())){
return true;
}
}
//System.out.print("false");
return false;
}
#Override
protected void onResume() {
bindService(new Intent(this, xService.class), mConnection, Context.BIND_AUTO_CREATE);
super.onResume();
}
#Override
protected void onPause() {
unbindService(mConnection);
super.onPause();
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
connection = null;
service = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder binder) {
//System.out.println("binding in hot listener");
service = ((xService.MyBinder)binder).getService();
connection = service.getConnection();
receives(connection);
Log.wtf("Service","connected");
}
};
void addNewMessage(ChatMessage m)
{
System.out.println("1");
messagex.add(m);
System.out.println("2");
adaptex.notifyDataSetChanged();
System.out.println("3");
getListView().setSelection(messagex.size()-1);
}
}
Here is my custom adapter (there is no problem in custom adapter but adding to make things clear) :
public class ChattingAdapter extends BaseAdapter{
private Context mContext;
private ArrayList<ChatMessage> mMessages;
public ChattingAdapter(Context context, ArrayList<ChatMessage> messages) {
super();
this.mContext = context;
this.mMessages = messages;
}
#Override
public int getCount() {
return mMessages.size();
}
#Override
public Object getItem(int position) {
return mMessages.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ChatMessage message = (ChatMessage) this.getItem(position);
ViewHolder holder;
if(convertView == null)
{
holder = new ViewHolder();
convertView = LayoutInflater.from(mContext).inflate(R.layout.listitem, parent, false);
holder.message = (TextView) convertView.findViewById(R.id.text1);
convertView.setTag(holder);
}
else
holder = (ViewHolder) convertView.getTag();
holder.message.setText(message.getMessage());
LayoutParams lp = (LayoutParams) holder.message.getLayoutParams();
//Check whether message is mine to show green background and align to right
if(message.isMine())
{ holder.message.setBackgroundResource(R.drawable.msgbox_new_selected_go_up);
lp.gravity = Gravity.RIGHT;
}
//If not mine then it is from sender to show orange background and align to left
else
{
holder.message.setBackgroundResource(R.drawable.msgbox_other_go_up);
lp.gravity = Gravity.LEFT;
}
holder.message.setLayoutParams(lp);
//holder.message.setTextColor(R.color.textColor);
return convertView;
}
private static class ViewHolder
{
TextView message;
}
#Override
public long getItemId(int position) {
//Unimplemented, because we aren't using Sqlite.
return position;
}
}
p.s: I am not storing any messages in sqlite as I dont want to restore messages for now, but I want new messages to be displayed at least onresume of activty. I can display sent messages after pressing send button but no received messages which works fine for the first time activity is created.
EDIT: I did more debugging , it turns out problem is not in resume activity , if I dont use receives() function for first time , and resume activity after going back , then receives() will work , that means , function inside receives() : getListView().setSelection(messagex.size()-1); works only once .
Either first time on receiving message or next time if and only if its not called first time on activity .
I think problem lies when you try to resume activity , you are still running the previous mHandler running and thus your instance of message is not destroyed and when you resume your activity it creates a problem . Make sure your mhandler destroys all instance of objects when unstop is called.
There's no place in your code where you save your messagex ArrayList. When you quit your activity by hitting back, your array get's distroyed (Garbage Collection takes care of it).
When you relaunch your activity your messagex ArrayList is created again, it's a brand new variable.
In fact, you're not relaunching your activity, you're creating a new instance.
EDIT:
I've never worked with the XMPPConnection objects before, but something else worth trying is the following:
When binding to the service, you're calling connection2.getChatManager().addChatListener and also arg0.addMessageListener but when unbinding you're not calling any removeXXX methods. I could be that since you're not removing your listeners, the whole XMPPConnection object still have references to the listeners that live in a dead Activity, and they are not being garbage collected.
Related
I want to make a volley http request only once and it should be during the time the app is installed.
I achieved this by making the http request in onCreate() method of SQLiteOpenHelper class which fetch data from remote MySQL ready for use. The problem I however runs into is that, after the app installation finishes, the app is presented with blank screen(fragment hosted on the main Activity). But when I close the app and opens for the second time, it is able to fetch data from the SQLite onto the screen.
Is there something special I have to do in the onCreate() method to ensure that the app runs only after the volley request finishes?
Here is my code.
SQLiteOpenHelper onCreate()
#Override
public void onCreate(final SQLiteDatabase db) {
db.execSQL(CREATE_NOTICE_TABLE);
db.execSQL(CREATE_ROSTER_TABLE);
/*Perform One time sync operations from remote MySQL*/
requestQueue = Volley.newRequestQueue(ContextGetter.getAppContext());
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, URL, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
if(response == null || response.length() == 0){
return;
}
if(response.has("notices")){
//Save to notices table
try {
JSONArray notices = response.getJSONArray("notices");
for (int i = 0; i < notices.length(); i++) {
JSONObject noticeObject = notices.getJSONObject(i);
String noticeID = noticeObject.getString(NOTICE_ID_KEY);
String noticeTitle = noticeObject.getString(NOTICE_TITLE_KEY);
String noticeBody = noticeObject.getString(NOTICE_BODY_KEY);
String dateCreated = noticeObject.getString(NOTICE_DATE_KEY);
NoticeItem noticeItem = new NoticeItem();
noticeItem.setId(Integer.parseInt(noticeID));
noticeItem.setTitle(noticeTitle);
noticeItem.setBody(noticeBody);
try {
noticeItem.setDate(formatDate(dateCreated));
} catch (ParseException e) {
e.printStackTrace();
}
//Save to SQLite
createNoticeBoard(noticeItem, db);
}
} catch (JSONException e) {
Log.d(TAG, "JSONException: " + e.getMessage());
}
}
//If roster available
if(response.has("rosters")){
//Save to roster table
try {
JSONArray rosters = response.getJSONArray("rosters");
for (int i = 0; i <rosters.length() ; i++) {
JSONObject rosterObject = rosters.getJSONObject(i);
String rosterID = rosterObject.getString(ROSTER_ID_KEY);
String rosterOwner = rosterObject.getString(ROSTER_OWNER_KEY);
String rosterDate = rosterObject.getString(ROSTER_DATE_KEY);
String rosterShift = rosterObject.getString(ROSTER_SHIFT_KEY);
//Check to verify that the user actually owns that roster later by using shared preference
RosterItem rosterItem = new RosterItem();
rosterItem.setSyncNumber(Integer.parseInt(rosterID));
rosterItem.setStaffNumber(rosterOwner);
rosterItem.setShift(rosterShift);
try {
rosterItem.setDate(formatDate(rosterDate));
} catch (ParseException e) {
e.printStackTrace();
}
createRoster(rosterItem, db);
}
}catch(JSONException e){
Log.d(TAG, "JSONException: "+ e.getMessage());
}
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "VolleyError "+error.getMessage());
}
});
//Add to requestQueue
requestQueue.add(request);
}
Fragment class
public class NoticeListFragment extends Fragment{
private static final String TAG = "NoticeListFragment";
private RecyclerView recyclerView;
private NoticeListAdapter mNoticeListAdapter;
public NoticeListFragment() {
//Requires empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Notices onCreate() called");
}
#Override
public void onResume() {
super.onResume();
updateUI(); //In case data changes
Log.d(TAG, "onResume() called");
}
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//Inflate layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_notice_list, container, false);
recyclerView = (RecyclerView) rootView.findViewById(R.id.rv_recycler_view);
recyclerView.setHasFixedSize(true);
LinearLayoutManager linearManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearManager);
updateUI();
return rootView;
}
/*View Holder*/
private class NoticeViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
private NoticeItem mNoticeItem;
public CardView mCardView;
public TextView mTextViewTitle;
public TextView mTextViewDate;
public TextView mTextViewBody;
public NoticeViewHolder(View itemView) {
super(itemView);
mCardView = (CardView) itemView.findViewById(R.id.card_view);
mTextViewBody = (TextView) itemView.findViewById(R.id.tv_notice_summary);
mTextViewTitle = (TextView) itemView.findViewById(R.id.tv_notice_title);
mTextViewDate = (TextView) itemView.findViewById(R.id.tv_notice_date);
itemView.setOnClickListener(this);
}
//Bind properties to views
private void bindNotice(NoticeItem noticeItem){
mNoticeItem = noticeItem;
mTextViewTitle.setText(noticeItem.getTitle());
mTextViewDate.setText(noticeItem.getDate());
mTextViewBody.setText(noticeItem.getSummary());
}
#Override
public void onClick(View view) {
Intent intent = NoticePagerActivity.newIntent(getActivity(), mNoticeItem.getId());
startActivity(intent);
}
}
/*Adapter*/
private class NoticeListAdapter extends RecyclerView.Adapter<NoticeViewHolder>{
//private Context mContext;
private List<NoticeItem> listItems;
//Provide a suitable constructor (depends on the kind of dataset you have)
public NoticeListAdapter(List<NoticeItem> data) {
//this.mContext = context;
this.listItems = data;
}
#Override
public NoticeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Create a new view
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.notice_lists_card, parent, false);
//Set the view size, margin, padding and layout parameters
NoticeViewHolder vh = new NoticeViewHolder(view);
return vh;
}
#Override
public void onBindViewHolder(NoticeViewHolder holder, int position){
final NoticeItem noticeItem = listItems.get(position);
//Bind data properties to views here...
holder.bindNotice(noticeItem);
}
#Override
public int getItemCount() {
return listItems.size();
}
public void setNotices(List<NoticeItem> notices){
listItems = notices;
}
}
//Bind adapter to recycler view
private void updateUI(){
NoticeLab noticeLab = NoticeLab.get(getActivity());
List<NoticeItem> notices = noticeLab.getNotices();
if(mNoticeListAdapter == null){
mNoticeListAdapter = new NoticeListAdapter(notices);
recyclerView.setAdapter(mNoticeListAdapter);
}else{
mNoticeListAdapter.setNotices(notices);
mNoticeListAdapter.notifyDataSetChanged();
}
}
}
I want to make a volley http request only once and it should be during the time the app is installed.
You do not get control when your app is installed.
Is there something special I have to do in the onCreate() method to ensure that the app runs only after the volley request finishes?
Volley is asynchronous. That is the complete and entire point behind using Volley. Immediately after you call requestQueue.add(request);, your onCreate() method continues executing, while Volley performs the network I/O on a background thread.
Some options are:
Get rid of all the Volley code, by packaging your starter data in the APK as an asset and using SQLiteAssetHelper to deploy the packaged database on first run of your app.
Do not use Volley. Instead, use something with a synchronous network I/O option (HttpURLConnection, OkHttp, etc.), and perform synchronous network I/O here. You should always be using your SQLiteOpenHelper subclass on a background thread, in case the database needs to be created or updated. So your onCreate() method of your SQLiteOpenHelper should always be called on a background thread, and you would not need yet another background thread for the network I/O. Then, you can be sure that by the time onCreate() ends that your starter data is there... except if you do not have Internet connectivity, or your server is down, etc.
Move all your initialization logic to something else, such as an IntentService. Have it create the database (using the IntentService's own background thread) and have it do the network I/O (again, using a synchronous API, since IntentService has its own background thread). Only start your UI once the IntentService is done with its work. You are in better position here to deal with connectivity errors via some sort of retry policy, while presenting some temporary UI to the user while that work is going on (e.g., ProgressBar).
I'm building a chat application, so I'm using two ListViews: one that shows the online friends and one for the chat itself, that receives the messages and so on. I'm using the XMPP protocol and the Smack Library for Android.
The Smack Library give me Listeners which are activated every time a friend status changes(online/offline) and the other one when the user receives a message. Here's how I declare the adapter and call an AsyncTask when the user press a button:
peopleList = (ListView) findViewById(R.id.peopleList);
adapter = new MyAdapter(this, people);
peopleList.setAdapter(adapter);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new ConnectAndLoad(MainActivity.this).execute();
}
});
Inside the AsyncTask, I connect to the server inside the doInBackground method and inside the onPostExecute I create the listener which adds the user to the array list of the listview and call adapter.notifyDataSetChanged();
public class ConnectAndLoad extends AsyncTask<String, Integer, Boolean> {
private ProgressDialog dialog;
public ConnectAndLoad(Activity activity)
{
this.dialog = new ProgressDialog(activity);
this.dialog.setTitle("Loading..");
this.dialog.setMessage("Connecting to the server..");
dialog.show();
}
#Override
protected Boolean doInBackground(String... arg0) {
MyConnectionManager.getInstance().setConnectionConfiguration(getApplicationContext());
MyConnectionManager.getInstance().connect();
MyConnectionManager.getInstance().login();
return true;
}
protected void onPostExecute(Boolean boo)
{
MyConnectionManager.getInstance().bored();
Roster roster = Roster.getInstanceFor(MyConnectionManager.getInstance().getConnection());
try
{
if (!roster.isLoaded()) roster.reloadAndWait();
}
catch (Exception e)
{
Log.e(TAG, "reload");
}
roster.addRosterListener(new RosterListener() {
public void entriesDeleted(Collection<String> addresses) {
}
public void entriesUpdated(Collection<String> addresses) {
}
public void entriesAdded(Collection<String> addresses) {
}
#Override
public void presenceChanged(Presence presence) {
people.add(new People(presence.getFrom(), presence.getStatus()));
adapter.notifyDataSetChanged();
}
});
dialog.dismiss();
}
}
And below is my Custom Adapter:
public class PeopleAdapter extends ArrayAdapter<People> {
private ArrayList<People> events_list = new ArrayList<>();
Context context;
public PeopleAdapter(Context context, ArrayList<People> users) {
super(context, 0, users);
this.context = context;
this.events_list = users;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
People user = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.people_list, parent, false);
}
TextView tvName = (TextView) convertView.findViewById(R.id.name);
TextView tvStatus = (TextView) convertView.findViewById(R.id.status);
tvName.setText(user.name);
tvStatus.setText(user.status);
convertView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(context, "You Clicked " + events_list.get(position).name, Toast.LENGTH_SHORT).show();
Intent i = new Intent(context, ConversationActivity.class);
i.putExtra("user", events_list.get(position).name);
context.startActivity(i);
}
});
return convertView;
}
}
I mean what I want to do I think it's a simple thing, every single chat app does it, is basically update the list view automatically but I'm having two problems:
The listview ONLY updates after I click on it. So it basically works
but I have to click on the listview..
I receive this error every time the list view updates (the app keeps working though):
Exception in packet listener: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
I can give you a simple solution. Make local Activity variable in the ConnectAndLoad class
private Activity activity;
public ConnectAndLoad(Activity activity)
{
...
activity.activity= activity;
}
Instead on directly calling adapter.notifyDataSetChanged(); use
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
adapter.notifyDataSetChanged();
}
});
It seems like presenceChanged() called in another thread. But be careful and make sure you delete RosterListener when activity gets destroyed or it can lead to the memory leaks i.e activity is already destroyed but you keep getting notifications about presence change.
I am trying to create an chat client even though i have an issue. When I open the app for first time and I open a chat (ListView) it works perfectly.
04-17 07:19:51.681 10020-10081/com.example.example E/SENDER ANSWER:﹕ yes
04-17 07:19:55.494 10020-10081/com.example.example E/SENDER ANSWER:﹕ hi
but when I leave the chat with the back buttom and i wanna get back to the window It doesnt display received messages but displays mines
after I sent "second" the other user sent :
04-17 07:21:17.246 10020-10081/com.example.example E/SENDER ANSWER:﹕ second time and doesnt work
and it didn't displayed on the listview.
here is my Code:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
chatmanager.addChatListener(new ChatManagerListener() {
#Override
public void chatCreated(Chat chat, boolean createdLocally) {
if (!createdLocally) {
chat.addMessageListener(new ChatMessageListener() {
#Override
public void processMessage(Chat chat, Message message) {
Log.e("SENDER ANSWER:", message.getBody());
sender_messages = message.getBody();
runOnUiThread(new Runnable() {
#Override
public void run() {
populateConversation(sender_messages, true);
populateConversationView();
}
});
}
});
}
}
});
}
maybe it doesn't have to be with the chat listener because it is receiving the messages but just not displaying. I would appreciate any help
public void populateConversationView(){
final ArrayAdapter<Message_Provider> adapter = new MyListAdapter();
messagesList.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
messagesList.setAdapter(adapter);
adapter.registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
messagesList.setSelection(adapter.getCount());
}
});
private class MyListAdapter extends ArrayAdapter<Message_Provider> { // Done
public MyListAdapter(){
super(getApplicationContext(), R.layout.chat_conversation, messages);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View itemView = convertView;
if(itemView == null){
itemView = getLayoutInflater().inflate(R.layout.source_message, parent, false);
}
Message_Provider currentContact = messages.get(position);
TextView myMessage = (TextView) itemView.findViewById(R.id.senderMessege);
myMessage.setText(currentContact.getMessage());
LinearLayout.LayoutParams parameter = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if (currentContact.getSender()){
parameter.gravity = Gravity.LEFT;
myMessage.setBackgroundResource(R.drawable.sender_messages);
}else {
parameter.gravity = Gravity.RIGHT;
myMessage.setBackgroundResource(R.drawable.my_message);
}
myMessage.setLayoutParams(parameter);
return itemView;
}
I was testing the whole day and it is like when i go back and i open a new activity the chat listener increments the previews one and not the new one and i think it is because of the runOnUiThread.
FIXED THIS, OMG! after hours of debugging XD I needed to close the chat in the listener
runOnUiThread(new Runnable() {
#Override
public void run() {
populateConversation(sender_messages, true);
populateConversationView();
chat.close(); // HERE
}
});
I post this because there are beginners like me and may get this problem too.
I have a chronometer in my list view. The problem is that sometimes it gets leaked i.e. i can see the OnChronometerTickListener executing every second even after i have navigated to another tab or scrolled the list view item out of view or even pressed the Home button. It seems a waste of resources, since i need it to run only when it is actually visible.
Is there a way to avoid this behaviour? Note that it happens only sometimes.
public class TimerLayout extends LinearLayout {
private static final String LOG_TAG = "TimerLayout";
Button btn_endTimer;
Button btn_cancelTimer;
Chronometer cmt_timer;
Runnable updateTimerThread;
Handler handler;
public TimerLayout(Context context, AttributeSet attrs) {
super(context,attrs);
setOrientation(LinearLayout.VERTICAL);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.timer, this, true);
cmt_timer = (Chronometer) getChildAt(0);
btn_endTimer = (Button) ((ViewGroup) getChildAt(1)).getChildAt(0);
btn_cancelTimer = (Button) ((ViewGroup) getChildAt(1)).getChildAt(1);
btn_endTimer.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View view){
cmt_timer.stop();
}
});
btn_cancelTimer.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View view){
cmt_timer.stop();
}
});
cmt_timer.setOnChronometerTickListener(new OnChronometerTickListener() {
#Override
public void onChronometerTick(Chronometer arg0) {
if(BuildConfig.DEBUG){Log.d(LOG_TAG,"onChronometerTick.objectid=" + System.identityHashCode(TimerLayout.this));}
}
});
}
public void init(Date startTime){
cmt_timer.stop();
if(startTime!=null){
Date now = new Date();
long elapsedTime = now.getTime() - startTime.getTime();
cmt_timer.setBase(SystemClock.elapsedRealtime() - elapsedTime);
cmt_timer.start();
}
}
}
I call the init method of this class in the bindView() method of my cursorAdaptor to start it.
The trick is to stop the chronometers in the onPause() of the fragment/activity.
So i create a class to hold the chronometers :
public class ChronometerHolder {
private WeakHashMap<Date, Chronometer> chronometerMap;
private static final String LOG_TAG = "ChronometerHolder";
public ChronometerHolder() {
chronometerMap = new WeakHashMap<Date, Chronometer>();
}
public void add(Date dt_startTime, Chronometer chronometer){
chronometerMap.put(dt_startTime, chronometer);
}
public void remove(Date dt_startTime){
chronometerMap.remove(dt_startTime);
}
public int getCount(){
return chronometerMap.size();
}
public void startAll() {
// start any chronometers that were paused
if (chronometerMap.size() > 0) {
Set<Entry<Date, Chronometer>> set = chronometerMap.entrySet();
Iterator<Entry<Date, Chronometer>> iterator = set.iterator();
Entry<Date, Chronometer> entry;
while (iterator.hasNext()) {
entry = (Entry<Date, Chronometer>) iterator.next();
entry.getValue().start();
}
}
}
}
public void stopAll() {
// stop any chronometers that might be running
if (chronometerMap.size() > 0) {
Set<Entry<Date, Chronometer>> set = chronometerMap.entrySet();
Iterator<Entry<Date, Chronometer>> iterator = set.iterator();
Entry<Date, Chronometer> entry;
while (iterator.hasNext()) {
entry = (Entry<Date, Chronometer>) iterator.next();
entry.getValue().stop();
}
}
}
}
Then i make the below changes :
Return the chronometer object from init() :
public Chronometer init(Date startTime){
Chronometer obj = null;
cmt_timer.stop();
if(startTime!=null){
Date now = new Date();
long elapsedTime = now.getTime() - startTime.getTime();
cmt_timer.setBase(SystemClock.elapsedRealtime() - elapsedTime);
cmt_timer.start();
obj = cmt_timer;
}
return obj;
}
In the fragment, instantiate the holder class :
ChronometerHolder chronometerHolder = new ChronometerHolder();
Every time you initialize the chrononmeter( in the bindView() of the CursorAdapter), add it to the holder :
Chronometer tmpChronometer = viewHolder.myTimer.init(dt_hitSessionStartTime);
if(tmpChronometer != null){
chronometerHolder.add(dt_hitSessionStartTime, tmpChronometer);
}
In onPause(), stop all the chronometers :
chronometerHolder.stopAll();
In onResume(), start all the chronometers :
chronometerHolder.startAll();
When the you press the Home button instead of exiting the app, and then open the app again, the bindView() calls are not executed. That means the chronometers are in a stopped state. So it has to be started in onResume() as done in #5.
I have an activity whose only purpose is to display a list view. There is a custom adapter to serve up the views for each element in the array.
I have break points all over and when I debug, it stops in "count" a number of times - the first few times the return value is zero, then it changes to 3 (the correct value in this case). Then we stop in "getView" - all the right stuff happens, and after we're through with all the break points, then presto magico all three records display on the screen. Yea!
So then I try to run the app outside of the debugger. I get the log message that it's visited "count", and the log message displays the return value so I know it's correct - but "getView" never gets called!!
I'm not sure which bits of code are relevant to this question & don't want to pollute the question with the entire project; please let me know if there's a specific section that would be helpful. I've researched all the "getView not called" questions but those consistently are for a case where getView never gets called, which clearly mine can be…sometimes :(
EDIT: Adapter code
public class DivisionAdapter extends BaseAdapter {
private static final String TAG = "DIV_ADAPT";
private ArrayList<Division> divisionList;
private Context context;
public DivisionAdapter(Context c, ArrayList<Division> divList) {
divisionList = divList;
context = c;
}
#Override
public int getCount() {
Integer count = 0;
if (divisionList != null) count = divisionList.size();
Log.v(TAG,count.toString());
return count;
}
#Override
public Object getItem(int position) {
Object o = null;
if (divisionList != null)
o = divisionList.get(position);
return o;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.v(TAG,"getView");
if (divisionList == null)
return null;
LinearLayout divisionView = null;
Division thisDiv = divisionList.get(position);
if (convertView == null) {
divisionView = new LinearLayout(context);
LayoutInflater li = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
li.inflate(R.layout.division_item, divisionView, true);
} else {
divisionView = (LinearLayout) convertView;
}
Log.v(TAG,thisDiv.name());
TextView v = (TextView) divisionView.findViewById(R.id.divisionName);
v.setText(thisDiv.name());
v = (TextView) divisionView.findViewById(R.id.divisionRegion);
v.setText(thisDiv.region());
return divisionView;
}
public void setList(ArrayList<Division> newList) {
divisionList = null;
divisionList = newList;
}
}
And just in case it's useful, some snippets from the activity class:
#Override
public void onResume() {
super.onResume();
refreshList();
}
private void refreshList() {
// use the class static query method to get the list of divisions
Division.query(Division.class,
new StackMobQuery().fieldIsEqualTo("status", "ACTIVE"),
new StackMobQueryCallback<Division>() {
#Override
public void failure(StackMobException arg0) {
// TODO Auto-generated method stub
Log.v(TAG, "query fail");
}
#Override
public void success(List<Division> arg0) {
Log.v(TAG, "query success");
divAdapt.setList((ArrayList<Division>) arg0);
divAdapt.notifyDataSetChanged();
}
});
}
EDIT 2/11:
I found this question: Markers not showing on map after stackmob query which reveals the hitherto unknown fact that stack mob queries run on a background thread. I'm starting to research the relationship between threads and adapters and thought I'd share this clue in case it helps anyone else figure out what's going on here faster than I can. TIA.
idk why this EVER worked in the debugger - that turned out to be a red herring.
As discovered, the StackMobModel static query method does run in a background thread, from which calling NotifyDataSetChanged() is completely ineffectual.
I ended up replacing the success method in the StackMobQueryCallback as follows:
#Override
public void success(final List<Division> arg0) {
Log.v(TAG, "query success");
runOnUiThread(new Runnable() {
public void run() {
updateList((ArrayList<Division>) arg0);
}
});
}
and then added this new method
private void updateList(ArrayList<Division> newList) {
divAdapt.setList(newList);
divAdapt.notifyDataSetChanged();
}
now, when the query returns, the adapter update is directed to run on the proper thread, and hooray, everything looks stitched together just fine and dandy.
whew!