I have one Fragment. For the OnClickListener() of all the views that are in the fragment I made another class UtilClickListener. There I am making db call on spinner onItemSelected using room persistence database. The database call first inserts data to the table and then updates an application variable in my application.
So I am trying to access the updated application variable on the spinner onItemSelected() just after the database call. But the variable is not updating at once, later when I click on other views of the fragment then I get the updated value.
Fragment code:
public class Calculator extends Fragment {
#Nullable
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Spinner ageSpinner = rootView.findViewById(R.id.spinner_how_old);
ageSpinner.setOnItemSelectedListener(new UtilOnClickListener(this));
CRSCalculatorAdapter ageListAdapter = new CRSCalculatorAdapter(rootView.getContext(),
android.R.layout.simple_spinner_item,Arrays.asList(rootView.getContext().getResources().getStringArray(R.array.age_group)) );
ageSpinner.setAdapter(ageListAdapter);
}
}
UtilOnClickListener class code:
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
switch (parentSpinnerId[1]) {
case "spinner_how_old}":
mGlobals.setAge(parent.getSelectedItem().toString());
CRSDatabaseInitializer.populateAsync(CRSDatabase.getDatabase(view.getContext()), crsCalculator.getContext(), Constants.QUERY_TYPE_ALL);
mListener.onChangeActionBarTitle(Integer.valueOf(mGlobals.getFinalScore())); // Here I am updating score in the action bar which is updating late on the next view click
break;
}
"CRSDatabaseInitializer" is calling an Asynctask for the database call.
Here is the database initializer code:
public class CRSDatabaseInitializer {
public static void populateAsync(#NonNull final CRSDatabase db, Context context, String typeOfQuery) {
PopulateDbAsync task = new PopulateDbAsync(db, typeOfQuery);
}
private static class PopulateDbAsync extends AsyncTask<Void, Void, Void> {
private final CRSDatabase mDb;
private final String mQueryType;
PopulateDbAsync(CRSDatabase db, String queryType) {
mDb = db;
mQueryType = queryType;
}
#Override
protected Void doInBackground(final Void... params) {
int scoreOfAge = Integer.valueOf(getScoreOnAge(mDb));
mGlobals.setFinalScore(scoreOfAge); // this is the application variable I need to update.
return null;
}
public static int getScoreOnAge(CRSDatabase db) {
int userScore = 0;
if (mGlobals.getAge() != null) {
userScore = Integer.valueOf(db.ageScoreDao().getScore(mGlobals.getAge(), mGlobals.getMarriedOrNot()));
}
return userScore;
}
}
Adding more codes from CRSDatabaseInitializer where I am inserting my data into the room database:
private static void insertNocCode(CRSDatabase db) {
NocCode nocData = new NocCode();
List<String[]> str = insertData(db, "nocCodes.csv");
for (int i = 0; i < str.size(); i++) {
nocData.setmNocCode(str.get(i)[0]);
nocData.setmTitle(str.get(i)[1]);
nocData.setmSkilltype(str.get(i)[2]);
addCRSData(db, nocData);
}
}
insertData(Database db, String filename); is the method where I am reading a csv file and inserting all the columns in the csv file.
public static List<String[]> insertData(CRSDatabase db, String fileName) {
String[] str = new String[5];
ArrayList<String[]> stringArray = new ArrayList<>();
try {
InputStreamReader is = new InputStreamReader(mContext.getAssets()
.open(fileName));
BufferedReader reader = new BufferedReader(is);
String line = "";
while ((line = reader.readLine()) != null) {
str = line.split(",");
stringArray.add(str);
}
} catch (IOException ex) {
ex.printStackTrace();
} finally
{
}
return stringArray;
}
And the insert method definition:
private static NocCode addCRSData(final CRSDatabase db, NocCode nocCode) {
db.nocCodeDao().insert(nocCode);
return nocCode;
}
So here is the update of this problem that I was going through. I solved the issue using handler. When I am making the database call, I am letting the DB to update the variable first , then I am running one handler to get the updated value later in the fragment.
Here is the code I updated in the UtilOnClickListener class.
private static class MyHandler extends Handler {}
private final MyHandler mHandler = new MyHandler();
public static class UtilRunner implements Runnable {
private final WeakReference<Calculator> mActivity;
public UtilRunner(Calculator activity) {
mActivity = new WeakReference<Calculator>(activity);
}
#Override
public void run() {
Calculator activity = mActivity.get();
if (activity.getContext() instanceof MainActivity) {
OnActionBarListener mListener = (OnActionBarListener) activity.getContext();
Globals mGlobals = (Globals) activity.getActivity().getApplicationContext();
mListener.onChangeActionBarTitle(mGlobals.getFinalScore());
}
}
}
And I am running the handler from OnClick of the views:
mHandler.postDelayed(mRunnable, 200);
There are various ways to handle this. In your case I am not able to understand why read operation is getting executed before inserted data is committed even though you are inserting and reading from the same thread. You can have a look on this discussion: stackOverFlow, what I learned from this discussion is that it's always better to take control in your hand because database internal implementation might change from time to time. Let's see the soution:
Wrap the read query inside a transaction either by using annotation #Transaction in Dao class or by wrapping the code for insertion in db.beginTransaction and db.endTransaction.devGuide. This ensures that read can't happen while database is being written.
What I find best for this is using Rx-Java See Introdution. You can do the insertion and then get notified when it completes and perform the read operation. Since insert operation will not return anything, wrap it inside Completable.fromAction. Completable observable has operator obs1.andThen(obs2), as clear from the name first obs1 is completed then only obs2 is executed. Note that your db.ageScoreDao().getScore(..) method should return an observable, hence wrap the return value in an Observable;
Completable.fromAction(new Action(){
#Override
public void run() throws Exception {
db.nocCodeDao().insert(nocCode);
}
}).andThen(return db.ageScoreDao().getScore(..)
.subscribeOn(Scheduler.io()) //do database query on io thread
.observeOn(AndroidScheduler.mainThread())
.subscribeWith(new DisposableObserver<Object>(){
#Override
public void onNext(Object o) {
//Here you update the variable
}
#Override
public void onError(Throwable e) {..}
#Override
public void onComplete() {..}
});
Related
I'm using an executor for background operations. I have a method that takes data from a Room Database and returns a string, so that I can send it from the repository into a viewmodel into activity.
How can I return a string in the method while there is a runnable in it? Please see the following code for a better description:
public String singleLoad(final int id){
DefaultExecutorSupplier.getInstance().forBackgroundTasks()
.execute(new Runnable() {
#Override
public void run() {
favNewsDao.loadSingle(id);
}
});
return favNewsDao.loadSingle(id);
}
The return gives an exception, saying that it cannot access the database on the Main Thread. How can I get a string from this method, like I have in this ViewModel class
public String singleLoad(int id) {
return repository.singleLoad(id);
}
Instead of using an Executor you can use an ExecutorService and submit a Callable. More information here: https://developer.android.com/reference/java/util/concurrent/ExecutorService.html#submit(java.util.concurrent.Callable%3CT%3E)
First Approach
In Repository class, use CountDownLatch with a count value of 1, and with until it reaches 0 to return back the correct result this can be achieved by using await() of this CountDownLatch which allows to urge executing the underlying code until the latch count value reaches 0.
CountDownLatch mLatch;
String singleLoad;
public String singleLoad(final int id){
mLatch = new CountDownLatch(1); // latch count is 1
DefaultExecutorSupplier.getInstance().forBackgroundTasks()
.execute(new Runnable() {
#Override
public void run() {
singleLoad = favNewsDao.loadSingle(id);
mLatch.countDown(); // Now you can allow returning back the result (id)
}
});
// Don't return now and wait until the Executer is done
try {
// Application's main thread awaits, till the
// CountDownLatch count value reaches 0
mLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return mId;
}
UPDATE
As first approach might have memory leak, here is a second approach by using a listener instead of CountDownLatch.
Second Approach
Here I am triggering a listener whenever the needed String is returned back from Room database to the Repository class; the listener is registered in the activity, cascaded to ViewModel, and then to the Repository.
The listener has a callback that retrieves the returned String from database which is eventually returned back to the activity.
Dao interface
#Dao
public interface MyDao {
...
#Query("SELECT text FROM notes WHERE id = :id") // change types according to your database
String loadSingle(int id);
}
Repository
public class AppRepository {
// ... non-relevent code is omitted
private static Executor executor = Executors.newSingleThreadExecutor();
public interface OnTextListener {
void onTextReceived(String text);
}
public void getNoteText(int id, OnTextListener listener) {
executor.execute(() -> {
String text = mDb.noteDao().loadSingle(id); // mDb is the database instance in repository
// trigger the listener in the ViewModel
listener.onTextReceived(text);
});
}
}
ViewModel
public class MainViewModel extends AndroidViewModel {
// ... non-relevent code is omitted
public void getNoteText(int id, AppRepository.OnTextListener listener) {
mRepository.getNoteText(id, listener);
}
}
Activity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... non-relevent code is omitted
// Retrieve single data from Room database
int id = 39;
mViewModel.getNoteText(id, new AppRepository.OnTextListener() {
#Override
public void onTextReceived(String text) {
// The retrieved text from Room database
Log.d(TAG, "onTextReceived: " + text);
new Handler(Looper.getMainLooper()) {
}.post(new Runnable() {
#Override
public void run() {
Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
}
});
}
});
}
}
I'm new to programming and have a problem.
I'm trying to get some value from room database.
The problem is when I try to get the method from viewModel to my fragment, android studio cannot resolve symbol.
my fragment:
public class Tab1Frag extends Fragment {
private Tab1FragModelView tab1ViewModel;
private RoomDatabase db;
public Tab1Frag() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
db = TransactionRoomDatabase.getDatabase(this);
//problem here
tab1ViewModel = ViewModelProviders.of(this).get(Tab1FragModelView.class);
tab1ViewModel.todaySum();
//and here
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_tab1, container, false);
}
The android studio underlined getApplicationContext on db as cannot resolve method and transactionEntity on tab1ViewModel.todaySum as cannot resolve symbol.
I don't know what the problem is because there's no problem in my another activity
here is a section of that activity
db = Room.databaseBuilder(getApplicationContext(), TransactionRoomDatabase.class,
"transactionEntity").build();
addSpendingDetailText = findViewById(R.id.addSpendingDetailText);
inputViewModel = ViewModelProviders.of(this).get(InputViewModel.class); //important
Button addSpending = findViewById(R.id.addSpendingBtn);
addSpending.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String transactionDetail = addSpendingDetailText.getText().toString();
String transactionAmount = hiddenAmount.getText().toString();
String transactionCategory = chooseCategory.getText().toString();
String transactionDate = hiddenDateText.getText().toString();
//Handler if field empty
if (transactionDetail.trim().isEmpty() || transactionAmount.trim().isEmpty() ||
transactionCategory.trim().isEmpty() || transactionDate.trim().isEmpty()) {
Toast toast1 = Toast.makeText(InputSpending.this,
"Please insert all required data", Toast.LENGTH_SHORT);
toast1.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
0, 275);
toast1.show();
return;
}
else {TransactionEntity transactionEntity = new TransactionEntity(transactionDate,
transactionCategory, transactionDetail, Double.parseDouble(transactionAmount)) {
};
inputViewModel.insert(transactionEntity);
I don't know if this gonna be needed or not, but i'll just post it just in case
my DAO
#Insert
void insertTransaction(TransactionEntity transactionEntity);
class SumToday { public double total;
}
#Query("SELECT SUM(transactionAmount) as total FROM `transaction` where date(transactionDate = 'now')")
double getTodayDateValue();
my fragment viewmodel
public class Tab1FragModelView extends AndroidViewModel {
private TransactionRepository repository;
public Tab1FragModelView(#NonNull Application application) {
super(application);
repository = new TransactionRepository(application);
}
public void todaySum(){
repository.todaySum();
}
}
my Repository
public class TransactionRepository {
private TransactionDao transactionDao;
public TransactionRepository(Application application) {
TransactionRoomDatabase db;
db = TransactionRoomDatabase.getDatabase(application);
transactionDao = db.transactionDao();
}
public void insert(TransactionEntity transactionEntity) {
new InsertTransactionAsyncTask(transactionDao).execute(transactionEntity);
}
private static class InsertTransactionAsyncTask extends AsyncTask<TransactionEntity, Void, Void> {
private TransactionDao transactionDao;
private InsertTransactionAsyncTask(TransactionDao transactionDao) {
this.transactionDao = transactionDao;
}
#Override
protected Void doInBackground(TransactionEntity... transactions) {
transactionDao.insertTransaction(transactions[0]);
return null;
}
}
public void todaySum() {
new TodaySumAsyncTask(transactionDao).execute();
}
private static class TodaySumAsyncTask extends AsyncTask<TransactionEntity, Void, Double> {
private TransactionDao transactionDao;
private TodaySumAsyncTask(TransactionDao transactionDao) {
this.transactionDao = transactionDao;
}
#Override
protected Double doInBackground(TransactionEntity... transactions) {
Double data = transactionDao.getTodayDateValue();
Log.d("TAG", "doInBackground: data :"+data);
// use livedata to notify viewmodel and then ui
return data;
}
}
EDIT:
+adding information:
My goal is to show the sum of value/amount from the database to my fragment's textView, no less no more.
To do that I'm trying to access it with viewmodel, which connect to repository, which connect to database. Then, I get this "cannot resolve symbol" error which is not happen when I'm inserting data to database. I've tried copying the working code of insert method to this fragment and it showing same "cannot resolve symbol" error. The only differences is I use that insert method in an activity, not fragment. I don't know if that the problem or not, but it maybe worth mentioning.
So far the apps is working until inserting data to database. When I check the database, the inputted data is there.
I don't mind changing my code as long as it can get my goal, but please don't put too advance code (because I'm still new to programming).
In the main activity, I have LiveData which contains members and a click listener. If I click on a member, then his ID is passed with intent.putExtra. That ID is later passed on to the method open in this activity. With this activity, I want to see the details of a member. In my MemberInfo activity, I marked a line where my problem lies.
It shows me this error: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
My DAO consists this code:
#Query("SELECT * FROM member_table WHERE MemberID=:id")
Member getMemberInfo(long id);
This is my main activity:
public class MemberMainActivity extends AppCompatActivity implements MemberListAdapter.MemberClickListener{
private MemberViewModel mMemberViewModel;
private List<Member> mMember;
void setMember(List<Member> members) {
mMember = members;
}
public static final int NEW_MEMBER_ACTIVITY_REQUEST_CODE = 1;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_member);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(MemberMainActivity.this, NewMemberActivity.class);
startActivityForResult(intent, NEW_MEMBER_ACTIVITY_REQUEST_CODE);
}
});
RecyclerView recyclerView = findViewById(R.id.recyclerviewcard_member);
final MemberListAdapter adapter = new MemberListAdapter(this);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
mMemberViewModel = ViewModelProviders.of(this).get(MemberViewModel.class);
mMemberViewModel.getAllMember().observe(this, new Observer<List<Member>>() {
#Override
public void onChanged(#Nullable final List<Member> members) {
mMember = members;
// Update the cached copy of the words in the adapter.
adapter.setMember(members);
}
});
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == NEW_MEMBER_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {
Member member = new Member(data.getStringExtra(NewMemberActivity.EXTRA_REPLY), data.getStringExtra(NewMemberActivity.EXTRA_REPLY2));
mMemberViewModel.insert(member);
} else {
Toast.makeText(
getApplicationContext(),
R.string.empty_not_saved,
Toast.LENGTH_LONG).show();
}
}
public void onMemberClick(int position) {
Member member = mMember.get(position);
Intent intent = new Intent(getApplicationContext(),MemberInfo.class);
intent.putExtra("MemberID", member.getId());
MemberInfo.open(this, member.getId());
}
}
This is my activity:
public class MemberInfo extends AppCompatActivity {
public static void open(Activity activity, long memberid) {
Intent intent = new Intent(activity, MemberInfo.class);
intent.putExtra("MemberID", memberid);
activity.startActivity(intent);
}
private List<Member> mMember;
private MemberViewModel mMemberViewModel;
void setMember(List<Member> members){
mMember = members;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memberinfo);
Log.i("okay", "memberinfo");
Intent intent = getIntent();
if (intent != null && intent.hasExtra("MemberID")) {
long memberid = intent.getLongExtra("MemberID", -1);
// TODO: get customer details based on customer id
TextView firstname = findViewById(R.id.layout_memberfirstname);
TextView surname = findViewById(R.id.layout_membersurname);
TextView balance = findViewById(R.id.layout_memberbalance);
-------------Member member = MemberRoomDatabase.getDatabase().memberDao().getMemberInfo(memberid);-------------
firstname.setText(member.getFirstname());
surname.setText(member.getSurname());
}
else {
Toast.makeText(
getApplicationContext(),
R.string.empty_not_saved,
Toast.LENGTH_LONG).show();
}
}
}
I thought that maybe it is because I'm missing a AsyncTask method. I tried this, but this also didn't work:
private static class insertMemberInfoAsyncTask extends AsyncTask<Member, Void, Void> {
private MemberDao mAsyncTaskDao;
insertMemberInfoAsyncTask(MemberDao dao) {
mAsyncTaskDao = dao;
}
#Override
protected Void doInBackground(Member... params) {
Member member = params[0];
mAsyncTaskDao.getMemberInfo(member.getId());
return null;
}
}
public Member getMemberInfo(long id) {
mAllMember = mMemberDao.getAllMember();
Member member = mMemberDao.getMemberInfo(id);
new insertMemberInfoAsyncTask(mMemberDao).execute(member);
return member;
}
I think I use the method wrong. Can anybody help me?
One option is to update your query to this:
#Query("SELECT * FROM member_table WHERE MemberID=:id")
LiveData<Member> getMemberInfo(long id);
(or similar, using Flowable). This avoids the need to manually create your own AsyncTask.
Returning the LiveData wrapper around the Member type automatically signals to Room that the query can/should be performed asynchronously. Per https://developer.android.com/training/data-storage/room/accessing-data (my emphasis):
Note: Room doesn't support database access on the main thread unless you've called allowMainThreadQueries() on the builder because it might lock the UI for a long period of time. Asynchronous queries—queries that return instances of LiveData or Flowable—are exempt from this rule because they asynchronously run the query on a background thread when needed.
You can use Future and Callable. So you would not be required to write a long asynctask and can perform your queries without adding allowMainThreadQueries() or using LiveData.
My dao query:-
#Query("SELECT * from user_data_table where SNO = 1")
UserData getDefaultData();
My repository method:-
public UserData getDefaultData() throws ExecutionException, InterruptedException {
Callable<UserData> callable = new Callable<UserData>() {
#Override
public UserData call() throws Exception {
return userDao.getDefaultData();
}
};
Future<UserData> future = Executors.newSingleThreadExecutor().submit(callable);
return future.get();
}
In my case, it works if you add Dispatcher.IO when you use coroutines:
viewModelScope.launch(Dispatchers.IO) {
//your database call
}
For me allowMainThreadQueries() works.
This allows room to support database access on the main thread.
See the following code
#Database(entities = [Word::class ],version = 1)
abstract class VocabularyDatabase:RoomDatabase() {
companion object {
private lateinit var INSTANCE:VocabularyDatabase
fun getInstance(context:Context):VocabularyDatabase= Room.databaseBuilder(
context,
VocabularyDatabase::class.java,
"vocabulary"
)
.createFromAsset("vocabulary.db")
.allowMainThreadQueries()
.build()
}
abstract fun dao():WordDao
}
Using Future and Callables can be an alternative here. By using Future and Callable you can get rid of AsyncTask and forcing your queries to the main thread.
The syntax would be as follow -
#Throws(ExecutionException::class, InterruptedException::class)
private fun canContinue(id: String): UserData{
val callable = Callable { userDao.getDefaultData() }
val future = Executors.newSingleThreadExecutor().submit(callable)
return future!!.get()
}
And, don't forget the null check for the data returned. Because it might be null
Java POJO Object
public class Section {
#ColumnInfo(name="section_id")
public int mSectionId;
#ColumnInfo(name="section_name")
public String mSectionName;
public int getSectionId() {
return mSectionId;
}
public void setSectionId(int mSectionId) {
this.mSectionId = mSectionId;
}
public String getSectionName() {
return mSectionName;
}
public void setSectionName(String mSectionName) {
this.mSectionName = mSectionName;
}
}
My Query method
#Query("SELECT * FROM section")
LiveData<List<Section>> getAllSections();
Accessing DB
final LiveData<List<Section>> sections = mDb.sectionDAO().getAllSections();
On the next line I am checking sections.getValue() which is always giving me null although I have data in the DataBase and later I am getting the value in the onChanged() method.
sections.observe(this, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sections){
}
});
But when I omit LiveData from the query I am getting the data as expected.
Query Method:
#Query("SELECT * FROM section")
List<Section> getAllSections();
Accessing DB:
final List<Section> sections = mDb.sectionDAO().getAllSections();
On the next line I am checking sections.getValue() which is always giving me null although I have data in the DataBase and later I am getting the value in the onChanged() method.
This is normal behavior, because queries that return LiveData, are working asynchronously. The value is null at that moment.
So calling this method
LiveData<List<Section>> getAllSections();
you will get the result later here
sections.observe(this, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sections){
}
});
from documentation:
Room does not allow accessing the database on the main thread unless you called allowMainThreadQueries() on the builder because it might potentially lock the UI for long periods of time. Asynchronous queries (queries that return LiveData or RxJava Flowable) are exempt from this rule since they asynchronously run the query on a background thread when needed.
I solve this problem through this approach
private MediatorLiveData<List<Section>> mSectionLive = new MediatorLiveData<>();
.
.
.
#Override
public LiveData<List<Section>> getAllSections() {
final LiveData<List<Section>> sections = mDb.sectionDAO().getAllSections();
mSectionLive.addSource(sections, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sectionList) {
if(sectionList == null || sectionList.isEmpty()) {
// Fetch data from API
}else{
mSectionLive.removeSource(sections);
mSectionLive.setValue(sectionList);
}
}
});
return mSectionLive;
}
LiveData is an asynchronous query, you get the LiveData object but it might contain no data. You could use an extra method to wait for the data to be filled and then extract the data.
public static <T> T getValue(LiveData<T> liveData) throws InterruptedException {
final Object[] objects = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer observer = new Observer() {
#Override
public void onChanged(#Nullable Object o) {
objects[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
latch.await(2, TimeUnit.SECONDS);
return (T) objects[0];
}
I resolved the similar issue as follows
Inside your ViewModel class
private LiveData<List<Section>> mSections;
#Override
public LiveData<List<Section>> getAllSections() {
if (mSections == null) {
mSections = mDb.sectionDAO().getAllSections();
}
return mSections;
}
This is all required. Never change the LiveData's instance.
I would suggest creating another query without LiveData if you need to synchronously fetch data from the database in your code.
DAO:
#Query("SELECT COUNT(*) FROM section")
int countAllSections();
ViewModel:
Integer countAllSections() {
return new CountAllSectionsTask().execute().get();
}
private static class CountAllSectionsTask extends AsyncTask<Void, Void, Integer> {
#Override
protected Integer doInBackground(Void... notes) {
return mDb.sectionDAO().countAllSections();
}
}
if sections.getValue() is null I have to call api for data and insert
in into the database
You can handle this at onChange method:
sections.observe(this, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sections){
if(sections == null || sections.size() == 0) {
// No data in your database, call your api for data
} else {
// One or more items retrieved, no need to call your api for data.
}
}
});
But you should better put this Database/Table initialization logic to a repository class. Check out Google's sample. See DatabaseCreator class.
For anyone that comes across this. If you are calling LiveData.getValue() and you are consistently getting null. It is possible that you forgot to invoke LiveData.observe(). If you forget to do so getValue() will always return null specially with List<> datatypes.
I am trying to create an app that contains 2 spinners with data from a data base. When a button is pressed, a new intent is created, showing the 2 spinners. The problem is that I have to create the DB queries in a new thread, and when I run the app, I get a null pointer exception (as far as my understanding goes, it is because the array in which I store the DB data is not yet populated).
My question is, how can I delay the creation of the spinners until the queries from the DB are made?
Below is a sample code of my implementation:
The intent where I create the spinners and make a call to a class that makes the DB queries:
String [][] dbData; //the array where i store data from DB
getDBdata myData = new getDBdata(); // create a new instance of the class that queries the DB
dbData = myData.getData(); // I get the data
Log.e("My log: ", String.valueOf(dbData.length)); //and here it crashes, giving me a null pointer exception
And the class where I create a new thread to make a DB query:
public getDBdata()
{
Thread thread = new Thread(new Runnable(){
#Override
public void run() {
DBconn(); //make a DB query
}
});
thread.start();
}
public String[][] getData()
{
return data;
}
The easiest way is a using of AsyncTask. The basic idea of AsyncTask is splitting execution of your task into three steps which go one after another. Each step is running in a separate thread. The last one runs in the Main(UI) where you can create your spinners. Sample:
public class DBQueryTask extends AsyncTask<Void, Void, String[][]> {
#Override
protected String[][] doInBackground(Void... params) {
DBconn();
String[][] a;
//...populating array
return a;//returning populated array
}
#Override
protected void onPostExecute(String[][] strings) {
//strings - array already populated
//this method runs on the Main thread. Therefore you can create your spinner
}
}
You don't need a new thread if you want to wait it to finish. But if you do, then you don't need to wait it to finish, but use a callback method instead
private static final int MESSAGE_DATA_RETRIEVED = 1;
private String [][] dbData; //the array where i store data from DB
private Handler mHandler;
// in onCreate or constructor depending what are you in
mHandler = new DataHandler(this);
// end of onCreate or constructor
private void getData() {
final getDBdata myData = new getDBdata(mHandler);
myData.getData();
}
private void onDataRetrieved() {
Log.e("My log: ", String.valueOf(dbData.length));
}
private static final class DataHandler extends Handler {
private final WeakReference<YourEnclosingClass> mClassReference;
DataHandler(final YourEnclosingClass instance) {
mClassReference = new WeakReference<>(instance);
}
#Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_DATA_RETRIEVED) {
final YourEnclosingClass instance = mClassReference.get();
if (instance != null) {
instance.onDataRetrieved();
}
}
}
}
The getting data class
private final Handler mHandler;
// calling a class a verb is bad. Should be a noun, but I haven't took a time to rename it here
public getDBdata(final final Handler handler) {
mHandler = handler;
// starting a Thread in constructor is a bad idea. I moved it to a method
}
public void getData()
{
final Thread thread = new Thread(new Runnable(){
#Override
public void run() {
DBconn(); //make a DB query
nHandler.sendEmptyMessage(MESSAGE_DATA_RETRIEVED);
}
});
thread.start();
}
That's how multithreading done.
But anyway that's a bad idea. You have to use AsyncTask or CursorLoader for your task instead.