Detecting when a value changed in a specific variable in Android - java

I have an int variable called mCurrentIndex. I want to do something when its value changes.
For example:
public class MainActivity extends Activity{
private int mCurrentIndex = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Apps logic.
}
public onCurrentIndexValueChange(){
button.setClickable(true);
}
}

One approach to solving this is using Android's LiveData.
In your situation, since you'd like to observe an int, you can do something like this:
public MutableLiveData<Integer> mCurrentIndex = new MutableLiveData<>();
To change your value, you would do this:
mCurrentIndex.setValue(12345); // Replace 12345 with your int value.
To observe the changes you would do the following:
mCurrentIndex.observe(this, new Observer<Integer>() {
#Override
public void onChanged(#Nullable final Integer newIntValue) {
// Update the UI, in this case, a TextView.
mNameTextView.setText("My new current index is: " + newIntValue);
}
};);
}
This approach is useful because you can define a ViewModel to segregate your logic from your Activity while having the UI observe and reflect the changes that occur.
By separating your logic out to the ViewModel, your logic becomes more easily testable since writing tests for your ViewModel is relatively easier than writing tests for your Activity.
For more information on LiveData check out this link.

you can use a setter function
private int mCurrentIndex = 1;
public void setCurrentIndex(int newValueFormCurrentIndex)
{
if(newValueFormCurrentIndex != mCurrentIndex)
{
onCurrentIndexValueChange();
mCurrentIndex = newValueFormCurrentIndex;
}
}

If the scope on mCurrentIndex is only in MainActivity then I think it would be best just to create getter and setter methods for mCurrentIndex and at the end of the setter method call onCurrentIndexValueChange()
public int getIndex(){
return this.mCurrentIndex;
}
public void setIndex(int value){
this.mCurrentIndex = value;
this.onCurrentIndexValueChange();
}

Related

android - Check if the room database is populated on startup without Livedata

I'm fairly new to Android and I want to have a database in my app.
I'm introduced to Room the documents say it's the best way to implement databases in the android.
Now I have to pre-populate some data in the database, and make sure that it gets populated before the app startup.
I see that there are many things like LiveData, Repositories, ViewModels and MediatorLiveData.
But I just want to keep it plain and simple, without using the said things how can one find if the database has been populated before the application launch.
I'm getting loads of NullPointerExceptions.
I'm using onCreateCallback() to populate the database but when I try to get the item from database it produces NullPointerException and after some time it may or may not produce the same warning, and the question remains the same what is the best way to know when the database is populated completely.
Here is a Minimal Example
public class MainActivity extends AppCompatActivity {
private TextView nameView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nameView = findViewById(R.id.name);
new NamesAsyncTask().execute();
}
private class NamesAsyncTask extends AsyncTask<Void,Void,String> {
private NameDao mNameDao;
#Override
public String doInBackground(Void... params) {
NameDatabase db = NameDatabase.getDatabase(MainActivity.this);
mNameDao = db.nameDao();
String name = mNameDao.getNameByName("Body").name;
return name;
}
#Override
public void onPostExecute(String name) {
nameView.setText(name);
}
}
}
Entity
#Entity(tableName = "name")
public class Name {
#NonNull
#PrimaryKey(autoGenerate = true)
public Integer id;
#NonNull
#ColumnInfo(name = "name")
public String name ;
public Name(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return this.id;
}
public void setId(Integer id ) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
Dao
#Dao
public interface NameDao {
#Insert
void insertAll(List<Name> names);
#Query("SELECT * from name")
List<Name> getAllNames();
#Query("DELETE FROM name")
void deleteAll();
#Query("SELECT * FROM name WHERE name = :name LIMIT 1")
Name getNameByName(String name);
#Query("SELECT * FROM name WHERE id = :id LIMIT 1")
Name getNameById(int id);
}
Database
#Database(entities = {Name.class}, version = 1)
public abstract class NameDatabase extends RoomDatabase {
public abstract NameDao nameDao();
private static NameDatabase INSTANCE;
public boolean setDatabaseCreated = false;
public static NameDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (NameDatabase.class) {
if (INSTANCE == null) {
INSTANCE = buildDatabase(context);
INSTANCE.updateDatabaseCreated(context);
}
}
}
return INSTANCE;
}
private static NameDatabase buildDatabase(final Context appContext) {
return Room.databaseBuilder(appContext, NameDatabase.class,
"name_database").addCallback(new Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
Executors.newSingleThreadScheduledExecutor().execute(() -> {
// Add Delay to stimulate a long running opeartion
addDelay();
// Generate the data for pre-population
NameDatabase database = NameDatabase.getDatabase(appContext);
List<Name> names = createNames();
insertData(database, names);
// notify that the database was created and it's ready to be used
database.setDatabaseCreated();
});
}
}
).build();
}
private void updateDatabaseCreated(final Context context) {
if (context.getDatabasePath("name_database").exists()) {
setDatabaseCreated();
}
}
private boolean setDatabaseCreated() {
return this.setDatabaseCreated = true;
}
protected static List<Name> createNames() {
List<Name> cList = new ArrayList<>();
cList.add(new Name(1, "Body"));
cList.add(new Name(2, "Mind"));
cList.add(new Name(3, "Love"));
cList.add(new Name(4, "Community"));
cList.add(new Name(5, "Career"));
cList.add(new Name(6, "Money"));
cList.add(new Name(7, "Fun"));
cList.add(new Name(8, "Home"));
return cList;
}
private static void insertData(final NameDatabase database, final List<Name> names) {
database.runInTransaction(() -> {
database.nameDao().insertAll(names);
});
}
private static void addDelay() {
try {
Thread.sleep(4000);
} catch (InterruptedException ignored) {
}
}
}
Gives me the exception on String name = mNameDao.getNameByName("Body").name; this line, when I install the app for first time, however if I close the app and start again it does not give the exception anymore. I think because the database has not been populated yet.
I read a post Pre-Populate Database that says on the first call to db.getInstance(context); the database will be populated on in my case NameDatabase.getDatabase(MainActivity.this).
So what shall I do to know if the database has finished populating after the call?
I think because the database has not been populated yet.
Correct. You have forked one background thread (AsyncTask). That thread is forking a second background thread, via your getDatabase() call, as your database callback is forking its own thread via Executors.newSingleThreadScheduledExecutor().execute(). Your AsyncTask is not going to wait for that second thread.
Remove Executors.newSingleThreadScheduledExecutor().execute() from your callback. Initialize your database on the current thread (which, in this case, will be the AsyncTask thread). Make sure that you only access the database from a background thread, such as by having your database access be managed by a repository.
I hope I'm not late! Just a bit of a background before I answer.
I was also searching for a solution regarding this problem. I wanted a loading screen at startup of my application then it will go away when the database has finished pre-populating.
And I have come up with this (brilliant) solution: Have a thread that checks the sizes of the tables to wait. And if all entities are not size 0 then notify the main UI thread. (The 0 could also be the size of your entities when they finished inserting. And it's also better that way.)
One thing I want to note is that you don't have to make the variables in your entity class public. You already have getters/setters for them. I also removed your setDatabaseCreated boolean variable. (Believe me, I also tried to have a volatile variable for checking but it didn't work.)
Here's the solution: Create a Notifier class that notifies the main UI thread when the database has finished pre-populating. One problem that arises from this is memory leaks. Your database might take a long time to pre-populate and the user might do some configuration (like rotating the device for example) that will create multiple instances of the same activity. However, we can solve it with WeakReference.
And here's the code...
Notifier class
public abstract class DBPrePopulateNotifier {
private Activity activity;
public DBPrePopulateNotifier(Activity activity) {
this.activity = activity;
}
public void execute() {
new WaitDBToPrePopulateAsyncTask(this, activity).execute();
}
// This method will be called to set your UI controllers
// No memory leaks will be caused by this because we will use
// a weak reference of the activity
public abstract void onFinished(String name);
private static class WaitDBToPrePopulateAsyncTask extends AsyncTask<Void, Void, String> {
private static final int SLEEP_BY_MILLISECONDS = 500;
private WeakReference<Activity> weakReference;
private DBPrePopulateNotifier notifier;
private WaitDBToPrePopulateAsyncTask(DBPrePopulateNotifier notifier, Activity activity) {
// We use a weak reference of the activity to prevent memory leaks
weakReference = new WeakReference<>(activity);
this.notifier = notifier;
}
#Override
protected String doInBackground(Void... voids) {
int count;
Activity activity;
while (true) {
try {
// This is to prevent giving the pc too much unnecessary load
Thread.sleep(SLEEP_BY_MILLISECONDS);
}
catch (InterruptedException e) {
e.printStackTrace();
break;
}
// We check if the activity still exists, if not then stop looping
activity = weakReference.get();
if (activity == null || activity.isFinishing()) {
return null;
}
count = NameDatabase.getDatabase(activity).nameDao().getAllNames().size();
if (count == 0) {
continue;
}
// Add more if statements here if you have more tables.
// E.g.
// count = NameDatabase.getDatabase(activity).anotherDao().getAll().size();
// if (count == 0) continue;
break;
}
activity = weakReference.get();
// Just to make sure that the activity is still there
if (activity == null || activity.isFinishing()) {
return null;
}
// This is the piece of code you wanted to execute
NameDatabase db = NameDatabase.getDatabase(activity);
NameDao nameDao = db.nameDao();
return nameDao.getNameByName("Body").getName();
}
#Override
protected void onPostExecute(String name) {
super.onPostExecute(name);
// Check whether activity is still alive if not then return
Activity activity = weakReference.get();
if (activity == null|| activity.isFinishing()) {
return;
}
// No need worry about memory leaks because
// the code below won't be executed anyway
// if a configuration has been made to the
// activity because of the return statement
// above
notifier.onFinished(name);
}
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
private TextView nameView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nameView = findViewById(R.id.name);
new DBPrePopulateNotifier(this) {
#Override
public void onFinished(String name) {
// You set your UI controllers here
// Don't worry and this won't cause any memory leaks
nameView.setText(name);
}
}.execute();
}
}
As you can see, our Notifier class has a thread in it that checks if the entities are not empty.
I didn't change anything in your other classes: Name, NameDao and NameDatabase except that I removed the boolean variable in NameDatabase and made private the variables in Name.
I hope that this answers your question perfectly. As you said, no LiveData, Repository, etc.
And I really hope I ain't late to answer!
Now I want to write down what I tried before I came up to the final solution.
Keep in mind that what I am trying to do here is for my app to show a progress bar (that infinite spinning circle) and put it away after the database has finished pre-populating.
Tried:
1. Thread inside thread
Practically, there's a thread that checks if the size of an entity is still 0. The query is done by another thread.
Outcome: Failed. Due to my lack of knowledge, you cannot start a thread within another thread. Threads can only be started from the main thread.
Tables' sizes loop checker
A thread that queries the tables to be checked if they have been initialized through an infinite loop. Only breaks if all sizes of the tables to be checked are greater than 0.
Outcome: Solution. This is by far the most elegant and working solution to this problem. It doesn't cause memory leaks because as soon as a configuration has been made, the thread that loops continually will break.
Static variable
A static volatile variable in the database class in which will turn to true when the thread has finished inserting the values.
Outcome: Failed. For unknown reason that I still search for, it won't run the thread for initializing the database. I have tried 3 versions of the code implementation but to no avail. Hence, a failure.
Initialize database then notify
A listener that is defined in the UI thread then passed by argument to the repository. All database initialization is done also in the repository. After populating the database, it will then notify/call the listener.
Outcome: Failed. Can cause memory leaks.
As always, happy coding!
Logging in onCreateCallback ofc!

Modify variables in an instance from another class Android

I simplified this for brevity; hopefully this example isn't actually functional. I'm creating and doing things with a variable, then I'm having another class do some stuff, then that class refers back to the original and tells it to do more stuff with that variable.
I've done exactly this with views. I simply pass the activity and then when I need to use it I use activity.findViewById(id) to do stuff. With variables, you can't just do activity.variable. I tried using a getter (as shown in this example), but maybe I'm still just doing it wrong or it can't be done how I'd like:
public class MyActivity {
private int test;
public void onCreate(Bundle savedInstanceState) {
test = 5;
int data = 100;
//Pass something to it
new NotAnActivity().func(MyActivity.this,data);
}
public int gettest() {
return test;
}
public void func(Activity instance, int response) {
int test = new MyActivity().gettest();
//Do stuff with test
}
}
public class NotAnActivity {
public void func(Activity instance, int data) {
//Do stuff with data
int response = 20;
//Try to pass information back
new MyActivity().func(instance,response);
}
}
You can't use a activity.gettest() because you're passing the superclass Activity between classes. To have access to the gettest() method you need to pass the specific child activity (MyActivity extends Activity, pass MyActivity instead of Activity) or you can cast to your specific activity.
((MyActivity)activity).getter();
So here, instead of:
public void func(Activity instance, int data) {
//Do stuff with data
int test = ((MyActivity)instance).gettest();
}
or
public void func(MyActivity instance, int data) {
//Do stuff with data
int test = instance.gettest();
}
It's not a good idea to instantiate your activities yourself new A()
public class MainActivity extends AppCompatActivity {
private int test;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test = 5;
int data = 100;
new NotAnActivity().func(this,data);
}
public int gettest() {
return test;
}
public void func(MainActivity instance, int response) {
//int test = new MainActivity().gettest();
int test = instance.gettest();
Log.v("variable", "test = " + test);
}
}
class NotAnActivity {
public void func(MainActivity instance, int data) {
//Do stuff with data
int response = 20;
//Try to pass information back
instance.func(instance,response);
}
}
You can try it. Your mistake is [new MyActivity()]

How to convert type Class<?> to int

I am trying to convert this data type to call out the method later on in another class to switch around layouts being made in other methods such as recipe1Layout(); by the index number of a class that has a field of a Class<?> Array.
Here is the getItem() method
public int getItem(){
int index = 0;
for(int i = 0; i < 100; i++) {
try{
index = recipe.getClass().getField("Classes").get(i);
} catch(Exception e){
}
}
return index;
}
Here is the Recipe Class
public class Recipes {
public Class<?>[] Classes = {
ChileConLecheActivity.class,
ArrozActivity.class,
EnchiladasActivity.class,
SopaActivity.class
};
}
The type of Class needs to be here because I have other uses for the recipe class.
For example, making a new instance of all classes to later on be called out to make adjustments to all the classes with one method.
The only thing I can think of is converting the type Class to an int so I can call out the method returning the index number I can do something like recipe.
index = Integer.parseInt(Classes[I].getName().toString());
But this is where I am asking for help I have no idea how to get rid of the error in the logcat.
The error shows up as
IndexOutOfArrayException
First off, stop using reflection. Use a public static array.
public class Recipes {
public static final Class<?>[] CLASSES = {
ChileConLecheActivity.class,
ArrozActivity.class,
EnchiladasActivity.class,
SopaActivity.class
};
}
Then, assuming your recipe instance has a field of what Class<Activity> it is assigned to, then, you would want something like this
public int getItem(){
int index = -1;
for(int i = 0 ; i < Recipe.CLASSES.length; i++) {
if (recipe.getActivityClass().equals(Recipe.CLASSES[i]) {
index = i;
break;
}
}
return index;
}
However, under certain situations, coupling one Activity class to any single Recipe instance, probably isn't a good idea.
I am trying to convert this data type to call out the method later on in another class to switch around layouts being made in other methods
if I understand what you are trying to do, you want a some mapping structure to some classes which have some pre-defined layouts.
Generally, this can be done with enums and OOP patterns
Have some base classes like this
public interface Layoutable {
int getLayout();
}
public enum Recipe {
ChileConLeche(R.layout.chile_con_leche),
Arroz(R.layout.arroz),
Enchiladas(R.layout.enchiladas),
Sopa(R.layout.sopa)
int layout;
Recipe(int layout) { this.layout = layout };
}
Ideally, you would want to use Fragments, but here is an example of an Activity structure
public abstract class RecipeActvity extends AppCompatActivity implements Layoutable {
protected Recipe recipe;
protected int getLayout() { return recipe.layout; }
}
public class ChileConLecheActivity extends RecipeActvity {
public ChileConLecheActivity() {
this.recipe = Recipe.ChileConLeche;
}
#Override
public void onCreate(...) {
setContentView(getLayout());
}
}
You can also combine this with a Map<Recipe, Class<RecipeActivity>>, from which you would use map.get(Recipe.ChileConCarne) to get the respective class element, for which you can startActivity() with

Java: How to do TextView.setText in another package?

I'm new to Java, I'm trying to build something in Android Studio.
The point is to 'push' a value for TextView baseTickVar in class BaseScreen, from another PACKAGE, where the class CoreFunctionality resides. Each time the tickNumber increases I want that shown in the TextView, so it's not a one time setting of text.
I have tried interfaces, but interfacing won't allow variables, only constants.
I've tried TextView.setText from the CoreFunctionality package, but that gave a nullpointerException and declaring the TextView to counter that didn't seem to help.
public class BaseScreen extends Activity implements View.OnClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base_screen);
// some irrelevant code here so i left it out.
TextView baseTickVar = (TextView)findViewById(R.id.baseTickVar);
baseTickVar.setText("1"); // just to not have it empty...
}
Now I want to set value of baseTickVar with a variable from the other package CoreFunctionality
public class CoreFunctionality extends Activity implements Runnable {
Thread tickThread = null;
volatile boolean playingGalactic;
long lastTick;
public int tickNumber;
int tickLength;
TextView baseTickVar;
public void controlTicks() {
tickLength = 2000;
long timeThisTick = (System.currentTimeMillis() - lastTick);
long timeToWait = tickLength - timeThisTick;
if (timeToWait > 0) {
try {
tickThread.sleep(timeToWait);
} catch (InterruptedException e) {
}
}
lastTick = System.currentTimeMillis();
}
#Override
public void run() {
while (playingGalactic) {
controlTicks();
tickNumber++;
Log.i("Tick number ", "" + tickNumber);
updateTick();
}
}
private void updateTick() {
// this is the whole point...
baseTickVar.setText("" + tickNumber);
}
public void resume() {
playingGalactic = true;
tickThread = new Thread(this);
tickThread.start();
}
I guess your BaseScreen is the main screen and CoreFunctionality is some component that is doing some work. Actually CoreFunctionality does not need to be Activity, it better fits to be a service.
You have to somehow pass reference to baseTickVar to the CoreFunctionality.
It is not allowed to mess with UI elements (such as TextView) from within another thread. You should consider using some inter-thread communication (such as Message).
Make BaseScreen to extend Handler or make a Handler object in it then override
public void handleMessage(Message msg) {
baseTickVar.setText("" + msg.obj);
}
In CoreFunctionality
private void updateTick() {
Message msg=new Message();
msg.obj=tickNumber;
h.sendMessage(msg);
}
Of course you'll have to pass the h reference to CoreFunctionality.
Maybe not 100% accurate but it should work with little tweaking.
Hope this will help.

Restrict access to only necessary data-class accessors / getters

I have a data class that is created in one class, and can be passed to one of several Android UI Activities that implement a specific Interface.
Each UI uses the same data, however, not all the UIs need all of the data. I was wondering if there was a simple solution that allows each UI to only use a specific part of the data (only use specific accessors / getters)
ClickListener Handler Class
// ICalculatorAbstraction is what all my UI's implement. It has method... void updateResult(ExpressionState expression)
public final View.OnClickListener listenerOn(final ICalculatorAbstraction UI) {
return listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
// Calculations
ExpressionState expression = new ExpressionState.Builder()
.setFirstNumber(num1)
.setSecondNumber(num2)
.setTotal(total)
.setOperator(operator.getSign())
.build();
UI.updateResult(expression);
}
};
}
ICalculatorAbstraction Interface
Again, all of my Android Activities (UIs) implement this interface
public interface ICalculatorAbstraction {
...
void updateResult(ExpressionState result);
}
Needs All Accessors UI
#Override
public void updateResult(ExpressionState result) {
String results = String.format( // NOTE: this one needs ALL the accessors / getters!!
"%s %s %s = %s",
result.getFirstNumber(),
result.getOperator(),
result.getSecondNumber(),
result.getTotal()
);
this.txtResult.setText(results);
Needs One Accessor UI
#Override
public void updateResult(ExpressionState result) {
String results = String.format( // NOTE: this one needs ONE accessor / getter!!
"Wow. Such Calcuation. %s",
result.getTotal()
// NOTE: These should not be allowed in this instance because this UI does not use them
// result.getFirstNumber()
// result.getOperator()
// result.getSecondNumber()
);
this.txtResult.setText(results);
How can I change updateResult(...) (both in the interface and in the UI) so that the specific UI's updateResult(...) will only let me use the needed assessors / getters?
I have tried to create a blank interface, and then created 2 abstract classes that implement that blank interface. The abstract classes had only the accessors / getters I needed, but when I tried to modify the above code, nothing worked.
Is this possible?
Update
Here is what I would like to see - "my best possible solution" you can say.
Needs All Accessors UI
#Override
public void updateResult(IAllAccessors result) {
String results = String.format(
"%s %s %s = %s",
result.getFirstNumber(),
result.getOperator(),
result.getSecondNumber(),
result.getTotal()
);
this.txtResult.setText(results);
Needs One Accessor UI
#Override
public void updateResult(IOneAccessorOnly result) {
String results = String.format(
"Wow. Such Calcuation. %s",
result.getTotal() // I should not be able to do result.getFirstNumber(); for example
);
this.txtResult.setText(results);
ExpressionState / Builder Class
public class ExpressionState implements IOneAccessorOnly, IAllAccessors {
private ExpressionState(Builder builder) { ... }
public double getFirstNumber() { ... } // IAllAccessors
public double getSecondNumber() { ... } // IAllAccessors
public String getOperator() { ... } // IAllAccessors
public double getTotal() { ... } // IAllAccessors, IOneAccessorOnly
public static class Builder { ... }
}
The problem with this solution, is that I cannot figure out what to do with my interface that I have above! I cannot have a parameter that will make the compiler happy with both UIs.
Update 2
I cannot have...
In my ClickListener class when creating ExpressionState with the builder
IOneAccessorOnly var = new ExperssionState.Builder()...
This is because in my ClickListener class, I don't know which one to create. It has to be very generic. In my UI's I want to simplify what I can use. I cannot do that with this approach
Because it does not know what to be, it has to be "everything"
ExpressionState var = new ExpressionState.Builder()...
It really cannot be anything other than that. The solution will have to deal with the UIs (Activities) specifically to narrow down what is allowed!!
If Expression state is your own class I'd make it implement two different interfaces like so.
public class ExpressionState implements InterfaceOne, InterfaceTwo {
public void interfaceOneGetterMethod()
public void interfaceTwoGetterMethod()
}
in another file:
public interface InterfaceOne {
public void interfaceOneGetterMethod();
and final file:
public interface InterfaceTwo {
public void interfaceTwoGetterMethod();
Now when you create the ExpressionState objects assign them to objects defined as:
InterfaceTwo var = new ExperssionState(blah);
Or modify your builder to just return interfaces( even better )

Categories