Android public static var in service - android

I'm trying to use statuc variable in my app to be used to keep tracking a certain variable, my service code, I've only pasted what's necessary
public class TestService extends Service {
public static HashMap<Long, Integer> testMap;
#Override
public void onCreate() {
registerReceiver();
testMap = new HashMap<Long, Integer>();
}
private final BroadcastReceiver testReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(UPDATE)) {
long key = intent.getLongExtra("key", -1);
int value = intent.getIntExtra("value", -1);
//Make sure I insert it once for testing purposes
if (testMap.get(key) == null)
testMap.put(key, value);
//This one prints the value fine
Log.i(TAG,testMap.get(key));
}
}
};
}
And then I try to access it inside my cursor adapter but I'm always getting null,
private static class MyCursorAdapter extends CursorAdapter {
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return inflater.inflate(R.layout.test_layout, parent, false);
}
#Override
public void bindView(View view, final Context context, Cursor cursor) {
Integer value = TestService.testMap.get(key);
//When I check value here, it's always null
if (value != null)
Log.i(TAG, value)
else
Log.i(TAG, "Key value is NULL")
}
}
What am I doing wrong?

Make sure you are not finishing your service, because everytime it calls onCreate() you are reinitializing your static variable. By the way, this is not a good approach, besides, static global variables are not good idea. You should communicate with your service via Intents or binding to it instead of sharing a static variable.

Related

RecyclerView only updates on app startup

newbie to Android here!
I've been learning how to implement SQLite in my app, and to sum it up, I have an Accountant class which has access to the SQLite database. The class pulls up the items from the database and puts them in an ArrayList. This ArrayList is what is used for my adapter for the recyclerView.
Whenever I add a new item in the app, the the item's data is stored in the database and the Accountant class's ArrayListgets updated with this info.
Then, the adapter calls its notifyDataSetChanged() method to update the View. This is where the problem occurs; the RecyclerView DOES display all items, but only upon app startup, NOT when a new item is added.
I've done all I can, it just LOOKS like it's supposed to work, but it doesn't and it's driving me nuts.
Here's the code
ItemAdapter Class
private class ItemAdapter extends RecyclerView.Adapter<ItemHolder> {
private List<Item> mItemList;
public ItemAdapter(List<Item> itemList) {
mItemList = itemList;
}
public ItemHolder onCreateViewHolder(ViewGroup parent, int ViewType) {
View view = getLayoutInflater().inflate(R.layout.list_item_item, parent, false);
return new ItemHolder(view);
}
public void onBindViewHolder(ItemHolder holder, int position) {
Item item = mItemList.get(position);
holder.bindItem(item);
}
public int getItemCount() {
return mItemList.size();
}
}
Accountant Class
public class Accountant {
private static Accountant sAccountant;
private double mTotalMoney;
private Context mContext;
private SQLiteDatabase mDatabase;
private List<Item> mItemList;
public static Accountant get(Context context) {
sAccountant = sAccountant == null ? new Accountant(context) : sAccountant;
return sAccountant;
}
private Accountant(Context context) {
mTotalMoney = 0;
mContext = context.getApplicationContext();
mDatabase = new ItemBaseHelper(mContext).getWritableDatabase();
mItemList = getListFromSQL();
}
private static ContentValues getContentValues(Item i) {
ContentValues values = new ContentValues();
values.put(ItemTable.cols.NAME, i.getName());
values.put(ItemTable.cols.PRICE, i.getPrice());
values.put(ItemTable.cols.COUNT, i.getCount());
return values;
}
public void addItem(Item item) {
ContentValues cv = getContentValues(item);
mDatabase.insert(ItemTable.NAME, null, cv);
mItemList = getListFromSQL();
}
public void removeItem(int i) {
}
public void addMoney(double money, boolean isSet) {
mTotalMoney += isSet ? money - mTotalMoney : money;
}
public String getTotalMoney() {
return MoneyUtils.prep(mTotalMoney);
}
public String getChange() {
double cost = 0;
for (Item item : getItemList())
cost += item.getPrice() * item.getCount();
return MoneyUtils.prep(mTotalMoney - cost);
}
public List<Item> getItemList() {
return mItemList;
}
private List<Item> getListFromSQL() {
List<Item> itemList = new ArrayList<>();
ItemCursorWrapper cursor = queryItems(null, null);
try {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
itemList.add(cursor.getItem());
cursor.moveToNext();
}
} finally {
cursor.close();
}
return itemList;
}
public ItemCursorWrapper queryItems(String whereClause, String[] whereArgs) {
Cursor cursor = mDatabase.query(ItemTable.NAME, null, whereClause, whereArgs, null, null, null);
return new ItemCursorWrapper(cursor);
}
public String individualPriceOf(Item i) {
return MoneyUtils.prep(i.getPrice());
}
public String totalPriceOf(Item i) {
return MoneyUtils.prep(i.getCount() * i.getPrice());
}
public String countOf(Item i) {
return String.valueOf(i.getCount());
}
public void clearList() {
mDatabase.delete(ItemTable.NAME, null, null);
}
}
Item adding logic
public void addItem(Item item) {
mAccountant.addItem(item);
mAdapter.notifyItemInserted(mAccountant.getListFromSQL().size() - 1);
mAdapter.notifyDataSetChanged();
mChangeButton.setText(mAccountant.getChange());
}
Well there is fundamental problem not even related to RecyclerView.
First let's see how to fix your issue then explanation of what's wrong.
change this
private List<Item> mItemList;
to this
private final List<Item> mItemList;
then instead of any assignment like mItemList = getListFromSQL(); write this
mItemList.clear();
mItemList.addAll(getListFromSQL());
Now explanation why your code is not working. The thing is that when you assign your dataSource (i.e. mItemList) to some new value you are changing reference to it (that's a java fundamental thing) so that your RecyclerView doesn't know anything about it and it's own dataSource which you assign only once in constructor remains the same old one which is not changed therefore your notifyDataSetChanged call does nothing.
General advice whenever using RecyclerView or a ListView make sure you define your dataSource as final.
This is happening because you do not add the item into your Adpater's list. Make a method inside your adapter and call this method from your Accountant class.
private class ItemAdapter extends RecyclerView.Adapter<ItemHolder> {
public void addItem(Item item) {
mItemList.add(item); ///Add the item to your arrayList and then notify
notifyItemInserted(mItemList.size());
}
When you add single item in Adapter dont call notifyDataSetChanged() method because it will notify the whole list. Instead only use notifyItemInserted() method.
Another think is make sure when you notify the adapter it must be from UI thread.
When you add your item then just call this adapter addItem() method from your Accountant class.
public void addItem(Item item) { ///This method is from Accountant Class
mAccountant.addItem(item);
mAdapter.addItem(item); // Call the addItem() from Adapter class
mChangeButton.setText(mAccountant.getChange());
}
Hope it will work...

Fragment's Context gets "lost" while passing it as a parameter

I came across a very awkward behaviour in my fragment.
The output is:
this.userID: 0
and
RoomChatFragment userID: 14
But in this case, this.userID should also be 14. Is my context lost somewhere, while passing it as a parameter? I can't explain myself this behaviour. I don't think getActivity() returns null, otherwise there would be an exception.
// Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
context = getActivity();
user = new UserHandler(context);
messageDatabase = MessageDatabase.getInstance(context);
Log.i("debug", "RoomChatFragment userID: " + user.getUserID());
}
// UserHandler
public class UserHandler {
private final SharedPreferences sharedPrefs;
private final SharedPreferences sharedPrefsPreferences;
private Context context;
public UserHandler(Context context) {
sharedPrefs = context.getSharedPreferences("USER", 0);
sharedPrefsPreferences = PreferenceManager.getDefaultSharedPreferences(context);
this.context = context;
}
public int getUserID() {
return sharedPrefs.getInt("userID", 0);
}
public void setUserID(int userID) {
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putInt("userID", userID);
editor.apply();
}
}
// Database
public class MessageDatabase extends AbstractDatabase {
private int userID;
protected static MessageDatabase instance;
public MessageDatabase(Context context) {
super(context);
UserHandler user = new UserHandler(context);
userID = user.getUserID();
}
public static MessageDatabase getInstance(Context context) {
if (MessageDatabase.instance == null) {
MessageDatabase.instance = new MessageDatabase(context);
}
return MessageDatabase.instance;
}
// ....
#Override
protected Message cursorToObject(Cursor cursor) {
Log.i("debug", "this.userID: " + this.userID);
}
}
// AbstractDatabase
public abstract class AbstractDatabase {
protected Context context;
protected AbstractDatabase(Context context) {
this.context = context;
}
}
I'm not absolutely sure what's going on here (your code is really messy). But it seems you're using a different key for the preference:
context.getSharedPreferences("USER", 0);
sharedPrefs.getInt("userID", 0);
Stupid me! The problem is the singleton design pattern of my database design. My MessageDatabase.instance is cached and holds an old Context object, where the userID of my SharedPreferences is 0.
I've updated my method like this and it seems to work:
public static MessageDatabase getInstance(Context context) {
if (MessageDatabase.instance == null || userID == 0) {
MessageDatabase.instance = new MessageDatabase(context);
}
return MessageDatabase.instance;
}

Adding a row to a growing database pre-release.

I have the database already finished and I would like to add a few rows that are present on the first time opening the app. The main issue, not knowing where in the application to implement this. For example, when the user opens the app for the first time, there is an example item. The item can be deleted. After the row is deleted it will never show up again. I am using Androrm (object relational mapper) androrm home page. My main question: How do I add a single row to the database (where & how) before release. Within the onCreate, will add a row each time, the class is opened.
Took out most code to make it simple.
Implementation
public class LogFirst extends Model {
protected CharField db_oneName;
public LogFirst() {
super(true);
db_oneName = new CharField(80);
}
public void setDB_oneName(String name1) {
db_oneName.set(name1);
}
public String getDB_oneName() {
return db_oneName.get();
}
#Override
public String toString() {
return db_oneName.get();
}
public static List<LogFirst> all() {
return LogFirst.objects().all().toList();
}
public static QuerySet<LogFirst> objects() {
return LogFirst.objects(context(), LogFirst.class);
}
public boolean save() {
Format formatter = new SimpleDateFormat("ddmmhhss");
String id = formatter.format(Calendar.getInstance().getTime()) + "";
return this.save(context(), Integer.valueOf(id));
}
public boolean delete() {
return this.delete(context());
}
private static Context context() {
return ExtendsActivity.context();
}
}
Saving
LogFirst lf = new LogFirst();
lf.setDB_oneName(name.getText().toString());
lf.save();
Adapter
public class LogFirstAdapter extends ArrayAdapter<LogFirst> {
Context mContext;
List<LogFirst> mLogs;
public LogFirstAdapter(Context context, int textViewResourceId, List<LogFirst> logs) {
super(context, textViewResourceId);
mContext = context;
mLogs = logs;
}
public void setLogs(List<LogFirst> logs) {
mLogs = logs;
}
public List<LogFirst> getLogs() {
return mLogs;
}
public void add(LogFirst log) {
mLogs.add(log);
}
public void remove(LogFirst log) {
mLogs.remove(log);
}
public int getCount() {
return mLogs.size();
}
public LogFirst getItem(int position) {
return mLogs.get(position);
}
public View getView(int position, View convertView, ViewGroup parent) {
LogFirstRow view = (LogFirstRow) convertView;
if (view == null) {
view = new LogFirstRow(mContext);
}
LogFirst log = getItem(position);
view.setLog(log);
return view;
}
}
Ideally you'd do something like that in a migration. I'm not familiar with Androrm but it looks like they have some support for migration: http://www.androrm.com/documentation/models/migrations/
Try putting in the code to create the new records in the overriden migrate function. The docs say migrations will be kept track of but I'm not sure how it will play out so test to see what happens.

update ListView after onStop()

I have a ListView in a fragment which I can add to from a BroadcastReceiver. However, when the app is removed from the "recents" panel (swipe the thumbnail of the app away - NOT choosing Force Stop in Settings) the BroadcastReceiver still runs (as it is supposed to do do when an app is removed from recents) but I get a Force Close dialog when it tries to update the ListView.
What I have gathered about what happens when removing an app from recents is that it does not kill the app, it just stops all the activiites. This means that the BroadcastReceivers and Services keep running. This is where my problem lies - I try to update the ListView in an Activity which has been stopped.
EDIT: I think that removing from recents causes onStop() to be called.
Do I need to create a service that update the ListView and keeps the activity running? Will it make any difference?
What I am trying to do is similar to say an SMS app. In an SMS app, a Broadcast is received and the ListView with the messages is updated to show the new message.
EDIT: Added some code
This is the Fragment which contains the ListView:
public class HistoryFragment extends FragmentBase implements OnItemAddedHandler {
ListView lv;
HistoryAdapter simpleAdpt;
int mPosition;
int index;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View histView = inflater.inflate(R.layout.history_fragment, container,
false);
setHasOptionsMenu(true);
ListView lv = (ListView) histView.findViewById(R.id.h_listView);
simpleAdpt = new HistoryAdapter();
lv.setAdapter(simpleAdpt);
return histView;
}
private class HistoryAdapter extends BaseAdapter {
private List<Map<String, Object>> mPlanetsList;
public HistoryAdapter() {
mPlanetsList = DataModel.getInstance().getPlanetList();
}
#Override
public int getCount() {
return mPlanetsList.size();
}
#Override
public Object getItem(int position) {
return mPlanetsList.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (null == convertView) {
convertView = LayoutInflater.from(getActivity()).inflate(
R.layout.history_item, null);
Log.i("convertView", "was null");
}
TextView tv_title = (TextView) convertView
.findViewById(R.id.hi_tv_title); // This is part of the layout of each item
HashMap<String, String> itemDataHashMap = (HashMap<String, String>) getItem(position);
tv_title.setText(itemDataHashMap.get("planet"));
return convertView;
}
}
#Override
public void onItemAdded(Object data) {
simpleAdpt.notifyDataSetChanged();
}
#Override
public void onItemRemove(int postion) {
simpleAdpt.notifyDataSetChanged();
}
}
This is the BroadcastReceiver that I am trying to use to add items to the ListView. It is fired using an AlarmManager. This means that there is time for the user to remove the app from the recents panel before the item is added to the ListView:
public class ReminderBroadcastReceiver extends BroadcastReceiver {
// This is declared in the manifest
#Override
public void onReceive(Context context, Intent intent) {
String title = "title";
DataModel.getInstance()
.addItem(title); // Add to History
}
}
In DataModel there is:
public static DataModel getInstance() {
if (null == instance) {
Log.i("getInstance", "null");
instance = new DataModel();
}
return instance;
}
private DataModel() {
initList();
}
private void initList() {
mHistoryList = History.getList();
for (int i = 0; i < mHistoryList.size(); i++) {
mPlanetsList.add(mHistoryList.get(i).createPlanet());
}
}
public void addItem(String title) {
History history = new History();
history.getDataHashMap().put("planet", title);
history.addToHistoryDB(); // This just adds to a Database
mHistoryList.add(0, history); // Help keep the orders the same
mPlanetsList.add(0, history.createPlanet());
if (null != mOnItemAddHandler) {
mOnItemAddHandler.onItemAdded(title);
}
}
If any more code is needed, please say

Use custom ProgressDialog during AsyncTask within Adapter

I use an ArrayAdapter to show items in a ListView. Every row in this ListView owns a button.
Whenever the user clicks on one of these buttons I start an AsyncTask to do some processing in the background.
This is working so far.
Now I want to show a custom ProgressDialog during this time. What puzzles me here is the first parameter of the static convinience method ProgressDialog.show(). Within an activity I usually use "Activityname.this" here. But what should I use in an adapter. I tried context from the adapter (that crashed), context.getApplicationContext and several more. Nothing worked - either crashed or is refused from the compiler.
So my question today: What should I put into this parameter?
Here's a stripped down part of my code:
public class MyAdapter extends ArrayAdapter<MyContainer> {
private class MyAsyncTask extends AsyncTask<String, Void, Boolean> {
#Override
protected void onPreExecute () {
if (!isRunning) {
progressDialog = MyProgressDialog.show(?????,
null,
null,
true,
false);
}
}
#Override
protected Boolean doInBackground(final String... strings) {
boolean rc = false;
if (!isRunning) {
isRunning = true;
//
rc = true;
}
return rc;
}
#Override
protected void onPostExecute(final Boolean result) {
if (progressDialog != null) {
progressDialog.cancel();
}
progressDialog = null;
//
}
}
private class MyOnClickListener implements OnClickListener {
private MyContainer container;
public MyOnClickListener(final MyContainer container) {
this.container = container;
}
public void onClick(final View view) {
if (container != null) {
new MyAsyncTask().execute(container.getUrl());
}
}
private String appName = "";
private ArrayList<MyContainer> containers;
private Context context;
private boolean isRunning;
private int layout;
private MyProgressDialog progressDialog;
private Resources resources;
public MyAdapter(final Context context, final int layout, final ArrayList<MyContainer> containers, final long link_id) {
super(context, layout, containers);
this.context = context;
this.layout = layout;
this.containers = containers;
resources = context.getResources();
appName = resources.getString(R.string.txt_appname);
}
#Override
public View getView(final int position, final View contentView, final ViewGroup viewGroup) {
//
}
}
Thanks in advance.
EDIT: Using the instance variable context from the adapter worked after cleaning the project. Arg! Thanks for your answers.
Hi :D well if you see your constructor of adapter
public MyAdapter(final Context context, final int layout, final ArrayList<MyContainer> containers, final long link_id) {
super(context, layout, containers);
this.context = context;
you pass context so in side of adapter you use context :D to build progress dialog

Categories

Resources