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

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!

Related

Android Room database #Insert method returns row id starting at 0

I have implemented the MVVM architecture in my app. I have my activity, viewmodel, repository, DAO, and database classes. The database holds objects that contain different lists that I want to switch between and have my RecyclerView show the currently selected list. My understanding is that row IDs in SQLite start at 1, but the insert method in my repository (which calls the #Insert method in my DAO) always starts with a row ID of 0. For testing purposes, I'm using LiveData to get all the objects in the database and when I log their IDs, they properly begin with 1 and go all the way to n. I don't want to maintain a list of all the objects in the database in memory, only the single object that contains the currently selected list.
When a user creates a new list (or selects an existing list), I want to have my RecyclerView display its contents and observe any changes to that list. I can't start observing an object without its proper corresponding ID.
How do I propagate the proper ID to MainActivity? If my ViewModel and Activity code are needed please tell me and I will edit the post.
When I log the ID from the Repository
When I log the LiveData in MainActivity the proper IDs show
My Database class
#TypeConverters({ArrayListConverter.class}) // List converter for database
#Database(entities = ListContainer.class, version = 1, exportSchema = false)
public abstract class ListContainerDatabase extends RoomDatabase {
private static ListContainerDatabase dbInstance;
private static final String DB_NAME = "list_container_db";
private static final int NUM_THREADS = 4;
final static ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
public abstract ListContainerDao getDao();
public static synchronized ListContainerDatabase getInstance(Context context) {
if(dbInstance == null) {
dbInstance = Room.databaseBuilder(
context.getApplicationContext(),
ListContainerDatabase.class, DB_NAME)
.fallbackToDestructiveMigration()
.build();
}
return dbInstance;
}
public static Executor getExecutor() {
return executor;
}
My DAO
#Dao
public interface ListContainerDao {
#Insert
long insertListContainer(ListContainer container);
... // other database queries
}
My Repository
public class Repository {
private static final String TAG = "Repository";
private ListContainerDao listContainerDao;
private long id;
private Executor executor = ListContainerDatabase.getExecutor();
public Repository(Application application) {
ListContainerDatabase containerDb = ListContainerDatabase.getInstance(application);
listContainerDao = containerDb.getDao();
}
public long insertListContainer(ListContainer container) {
executor.execute(new Runnable() {
#Override
public void run() {
id = listContainerDao.insertListContainer(container); // this returns ID starting at 0
}
});
Log.i(TAG, "insertListContainer: from Repository id is :" + id);
return id;
}
}
You should consider the asynchronity of your code here:
private long id;
public long insertListContainer(ListContainer container) {
// Timestamp #1. This started, first time id is not initialised, equals to 0
executor.execute(new Runnable() {
#Override
public void run() {
id = listContainerDao.insertListContainer(container);
// Timestamp #3. This returns you id = 1, but with some delay, so this value changed id and will be returned only next time you call the method
}
});
// Timestamp #2. Main thread isn't blocked, so it doesn't wait for runnable to be executed as well and returns first time id = 0, next time - changed value - 1, and so on
Log.i(TAG, "insertListContainer: from Repository id is :" + id);
return id;
}
If you want to get result of asynchronous operation in Java you can use:
callback (something like in this answer)
RxJava / CompletableFuture

How to get inserted row id after inserting row in room to the main thread

I want to return the inserted row Id to use it to update some value in the same row
#Entity(tableName = "course")
public class Course {
#PrimaryKey(autoGenerate = true)
private int id;
private String firebaseId;
}
#Dao
public interface CourseDao {
#Insert(onConflict = REPLACE)
long insertCourse(Course course);
#Query("UPDATE course SET firebaseId = :firebaseId WHERE id = :id")
void updateFirebaseId(int id, String firebaseId);
}
the problem is I cant return the Id to the main thread
public class Repository {
private static final Object LOCK= new Object();
private static Repository sInstance;
private final CourseDao mCourseDao;
private final AppExecutors mAppExecutors;
public void insertCourse(final Course course) {
mAppExecutors.diskIO().execute(new Runnable() {
#Override
public void run() {
mCourseDao.insertCourse(course);
}
});
}
public void updateCourse(final Course course) {
mAppExecutors.diskIO().execute(new Runnable() {
#Override
public void run() {
mCourseDao.updateCourse(course);
}
});
}
}
I tried to use liveData but its not supported on insert
Error:(34, 20) error: Methods annotated with #Insert can return either void, long, Long, long[], Long[] or List<Long>.
Is it possible to return the id of Course once the insertion is completed without writing a separate select query?
LiveData is not supported for insert.
I feel there are 2 approaches to do insert operation in the background thread and send the result (long) back to Activity:
Using LiveData, I personally like this approach:
public class Repository {
private LiveData<Long> insertedId = MutableLiveData()
public void insertCourse(final Course course) {
mAppExecutors.diskIO().execute(new Runnable() {
#Override
public void run() {
Long id = mCourseDao.insertCourse(course);
insertId.setValue(id);
}
});
}
}
Using Rx:
return Single.fromCallable(() -> mCourseDao.insertCourse(course));
Here, you'll get Single<Long> which you can observe in your Activity
Note: Repository will return Long to ViewModel and in your ViewModel will have the LiveData and setValue stuff.
I did it like this in Java
In the environment where you want the id:
public class MyIdClass {
ArrayList<Integer> someIds = new ArrayList<>(); //this could be a primitive it doesn't matter
constructor with repository....{}
#FunctionalInterface //desugaring... normal interfaces are ok I guess?
public interface GetIdFromDao {
void getId(long id);
}
public void insertSomething(
Something something
) {
someRepository.insertSomething(
something,
id -> someIds.add((int)id) //lambda replacement, I think method reference cannot be done because of (int) casting, but if Array is type long it could be done.
);
}
}
In abstract class MyDao...: (something that I cannot stress enough..., work with ABSTRACT CLASS DAO, its more flexible)
#Insert(onConflict = OnConflictStrategy.IGNORE)
protected abstract long protectedInsertSomething(Something something);
public void insert(
Something something,
MyIdClass.GetIdFromDao getIdFromDao
) {
//get long id with insert method type long
long newSomethingId = protectedInsertSomething(something);
getIdFromDao.getId(newSomethingId);
}
Inside Repository, If you use AsyncTask<X, Y, Z>, you can actually pass everything through VarAgrs, even listeners, but be sure to recast them in the order which they get inserted, and use type Object or a common ancestor.
(Something) object[0];
(MyIdClass.GetIdFromDao) object[1];
new InsertAsyncTask<>(dao).execute(
something, //0
getIdFromDao //1
)
Also use #SupressWarnings("unchecked"), nothing happens
Now If you want even further interaction, you can connect a LiveData to the listener, or construct a Factory ViewModel...
an abstract factory ViewModel... that would be interesting.
But I believe DataBinding has an Observable view Model which I guess can be used(?)... I really don't know.

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.

Multithreading refresh UI in Vaadin

I'm trying to implement a multithreading refresh UI in my Vaadin app.
Part of this UI is a dChart based on container. To build dChart Im iterating through container and count elements by one of their properties, like this:
Collection<?> itemIDS = container.getItemIds();
for (Object itemID : itemIDS) {
Property property = container.getContainerProperty(itemID, "status");
String status = (String) property.getValue();
if (countMap.containsKey(status)) {
countMap.put(status, countMap.get(status) + 1);
} else {
countMap.put(status, 1);
}
}
However it takes over 2-3 seconds if container has thousands of elements.
User just can't wait so long to refresh an UI.
I read that i can build my UI and later just refresh it using #Push Vaadin annotation, after dChart is fully built.
So i build something like this:
{
//class with #Push
void refreshPieDChart(GeneratedPropertyContainer container) {
new InitializerThread(ui, container).start();
}
class InitializerThread extends Thread {
private LogsUI parentUI;
private GeneratedPropertyContainer container;
InitializerThread(LogsUI ui, GeneratedPropertyContainer container) {
parentUI = ui;
this.container = container;
}
#Override
public void run() {
//building dChart etc etc... which takes over 2-3 seconds
// Init done, update the UI after doing locking
parentUI.access(new Runnable() {
#Override
public void run() {
chart.setDataSeries(dataSeries).show();
}
});
}
}
}
However if i refresh page few times, it is generating errors about SQLContainer:
java.lang.IllegalStateException: A trasaction is already active!
Becouse after few refreshes, my multiple threads are running parallel using the same SQLContainer.
To fix that, I want to stop all working refresh threads except last one to eliminate concurrent problem. How i can do it? Maybe other solution?
EDIT:
I have tried smth like this, but problem still remains, is it a correct way to prevent concurrent problem?
{
private static final Object mutex = new Object();
//class with #Push
void refreshPieDChart(GeneratedPropertyContainer container) {
new InitializerThread(ui, container).start();
}
class InitializerThread extends Thread {
private LogsUI parentUI;
private GeneratedPropertyContainer container;
InitializerThread(LogsUI ui, GeneratedPropertyContainer container) {
parentUI = ui;
this.container = container;
}
#Override
public void run() {
//is it correct way to prevent concurrent problem?
synchronized(mutex){
//method to refresh/build chart which takes 2-3s.
}
// Init done, update the UI after doing locking
parentUI.access(new Runnable() {
#Override
public void run() {
chart.setDataSeries(dataSeries).show();
}
});
}
}
}
This is what i did:
Henri Kerola points me pretty obvious idea: do counting in SQL. As i said I was thinking about that but that would means I need to prepare SQL for every possible filters combination. That is a pretty complicated and doesn't look good.
But this realised me that if I'm using SQLContainer with filters for my table, I can do the same for counting. I just need to create second SQLContainer with my ownFreeformQuery and FreeformStatementDelegate.
If i will create all of above, i can just add THE SAME filters to both containers however i dont need to count elements now becouse 2nd container holds values for me. It sounds complecated but take a look on my code:
FreeformQuery myQuery = new MyFreeformQuery(pool);
FreeformQuery countQuery = new CountMyFreeformQuery(pool);
SQLContainer myContainer = new SQLContainer(myQuery); //here i hold my all records as in a Question
SQLContainer countContainer = new SQLContainer(countQuery); //here i hold computed count(*) and status
MyFreeformQuery.java looks like:
class ProcessFreeformQuery extends FreeformQuery {
private static final String QUERY_STRING = "SELECT request_date, status, id_foo FROM foo";
private static final String COUNT_STRING = "SELECT COUNT(*) FROM foo";
private static final String PK_COLUMN_NAME = "id_foo";
MyFreeformQuery(JDBCConnectionPool connectionPool) {
super(QUERY_STRING, connectionPool, PK_COLUMN_NAME);
setDelegate(new AbstractFreeformStatementDelegate() {
protected String getPkColumnName() {
return PK_COLUMN_NAME;
}
protected String getQueryString() {
return QUERY_STRING;
}
protected String getCountString() {
return COUNT_STRING;
}
});
}
and most important CountFreeformQuery.java looks like:
public class CountFreeformQuery extends FreeformQuery {
private static final String QUERY_STRING_GROUP = "SELECT status, count(*) as num FROM foo GROUP BY status";
private static final String QUERY_STRING = "SELECT status, count(*) as num FROM foo";
private static final String GROUP_BY = "foo.status";
public CountFreeformQuery(JDBCConnectionPool connectionPool) {
super(QUERY_STRING_GROUP, connectionPool);
setDelegate(new CountAbstractFreeformStatementDelegate() {
protected String getQueryString() {
return QUERY_STRING;
}
protected String getGroupBy(){
return GROUP_BY;
}
});
}
}
Now if i want to refresh dChart after smth like that:
myContainer.addContainerFilter(new Between(DATE_PROPERTY, getTimestamp(currentDate), getOneDayAfter(currentDate)));
I just do the same with countContainer:
countContainer.addContainerFilter(new Between(DATE_PROPERTY, getTimestamp(currentDate), getOneDayAfter(currentDate)));
And pass it to method which dont need to count elements, just add all container to map and then to dChart like that:
Map<String, Long> countMap = new HashMap<String, Long>();
Collection<?> itemIDS = container.getItemIds();
for (Object itemID : itemIDS) {
Property statusProperty = container.getContainerProperty(itemID, "status");
Property numProperty = container.getContainerProperty(itemID, "num");
countMap.put((String) statusProperty.getValue(), (Long) numProperty.getValue());
}
Now i have statuses of elements in myContainer counted, no need to multithreading or writing tons of sql.
Thanks guys for suggestions.

Inner class can access but not update values - AsyncTask

I am trying to unzip a folder using Android's AsyncTask. The class (called Decompress) is an inner class of Unzip where Unzip itself is a non-Activity class. The pseudo-code is:
public class Unzip {
private String index;
private String unzipDest; //destination file for storing folder.
private Activity activity;
private boolean result; //result of decompress.
public void unzip(String loc) {
Decompress workThread = new Decompress(loc, activity);
workThread.execute();
if(unzip operation was successful) {
display(index);
}
//Class Decompress:
class Decompress extends AsyncTask<Void, Integer, Boolean> {
private ProgressDialog pd = null;
private Context mContext;
private String loc;
private int nEntries;
private int entriesUnzipped;
public Decompress(String location, Context c) {
loc = location;
mContext = c;
nEntries = 0;
entriesUnzipped = 0;
Log.v(this.toString(), "Exiting decompress constructor.");
}
#Override
protected void onPreExecute() {
Log.v(this.toString(), "Inside onPreExecute.");
pd = new ProgressDialog(mContext);
pd.setTitle("Unzipping folder.");
pd.setMessage("Unzip in progress.");
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
Log.v(this.toString(), "Showing dialog and exiting.");
pd.show();
}
#Override
protected Boolean doInBackground(Void... params) {
//unzip operation goes here.
unzipDest = something; //unzip destination is set here.
if(unzip operation is successful) {
result = true;
index = url pointing to location of unzipped folder.
} else {
result = false;
}
}
#Override
protected void onPostExecute(Boolean result) {
if(result) {
if(pd != null) {
pd.setTitle("Success");
pd.setMessage("folder is now ready for use.");
pd.show();
pd.dismiss();
pd = null;
Log.v(this.toString(), "Unzipped.");
index = unzipDest + "/someURL";
Log.v(this.toString(), "index present in: " + index);
}
} else {
pd = ProgressDialog.show(mContext, "Failure", "Cannot unzip.");
pd.dismiss();
}
}
}
Problems I am facing:
1. The value of unzipDest and index, updated in doInBackground, remain null to Unzip and all its objects. How can I ensure that the values remain updated?
2. I know that doInBackground occurs in a thread separate from the main UI thread. Does that mean that any values updated in the new thread will be lost once that thread returns?
How can I ensure that the values remain updated?
They will be updated since they are member variables. However, since AsyncTask is asynchrounous, they might not be updated yet when you check them. You can use an interface to create a callback when these values are updated. This SO answer covers how to do this
Does that mean that any values updated in the new thread will be lost once that thread returns?
No they shouldn't be "lost". They probably just haven't been changed in the AsyncTask when you check them.
Since this isn't your actual code I can't see when you are trying to access them but you can use the interface method or call the functions that need these values in onPostExecute(). You also can do a null check before trying to access them. It just depends on the functionality and flow that you need as to which is the best way. Hope that helps.
Edit
In the answer I linked to, you tell the Activity that you will use that interface and override its method(s) with implements AsyncResponse in your Activity declaration after creating the separate interface class
public class MainActivity implements AsyncResponse{
then, in your Activity still, you override the method you declared in that class (void processFinish(String output);)
#Override
void processFinish(String output){ // using same params as onPostExecute()
//this you will received result fired from async class of onPostExecute(result) method.
}
then this is called in onPostExecute() when the listener sees that it is done with delegate.processFinish(result); delegate is an instance of AsyncResponse (your interface class)
public class AasyncTask extends AsyncTask{
public AsyncResponse delegate=null;
#Override
protected void onPostExecute(String result) {
delegate.processFinish(result);
}
Interface example taken from linked answer above and adjusted/commented for clarity. So be sure to upvote that answer if it helps anyone.

Categories