I'm a newcomer on Android and in the Google Guide I'm in this part: http://developer.android.com/training/basics/data-storage/databases.html.
Yes, it's about the dabatase, but to put it in practice, in my dummy studying app I created a simple system for store "profiles" to have something to store in the database. Basically, I've created an Activity for fill some text fields and store it in the database. And a class called ProfileDao that do the CRUD stuff.
I've reached a point where I want to build an activity to show all profiles added, using a LinearLayout. The problems is that I can't stack the views in code as the XML does.
Here is some code:
The ProfileFragment class:
public final class ProfileFragment extends Fragment {
private String _name;
private String _gender;
private int _age;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View retVal = inflater.inflate(R.layout.profile_fragment, container, false);
final TextView _nameField = (TextView) retVal.findViewById(R.id.profile_fragment_name);
final TextView _genderField = (TextView) retVal.findViewById(R.id.profile_fragment_gender);
final TextView _ageField = (TextView) retVal.findViewById(R.id.profile_fragment_age);
_nameField.setText(_name);
_genderField.setText(_gender);
_ageField.setText(String.valueOf(_age));
return retVal;
}
public void setName(String name) { _name = name; }
public void setGender(String gender) { _gender = gender;}
public void setAge(int age) { _age = age; }
public String getName() { return _name; }
public String getGender() { return _gender; }
public int getAge() { return _age; }
}
My intent initially was to use this fragment to dinamically put it in a LinearLayout, but I quickly saw I can't do this and made this class just a util for return a view.
The SeeProfilesActivity:
public class SeeProfilesActivity extends Activity {
private static final String TAG = makeLogTag(SeeProfilesActivity.class);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.see_profiles_activity);
final ProfileDao profDao = new ProfileDao(new ProfileDbHelper(this));
final List<Profile> profiles = profDao.getProfiles();
LOGD(TAG, "PROFILES RETRIEVED");
if(profiles.size() == 0) {
final TextView thereisNotProfiles = new TextView(this);
thereisNotProfiles.setText(getString(R.string.there_is_not_profiles));
thereisNotProfiles.setTextSize(35);
LOGD(TAG, "NO PROFILES, SO JUST DISPLAY THERE IS NOT");
setContentView(thereisNotProfiles);
return;
}
addFragmentProfiles(profiles);
}
private void addFragmentProfiles(List<Profile> profiles) {
final LinearLayout itLayout = (LinearLayout) ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
itLayout.setOrientation(LinearLayout.VERTICAL);
View currView;
for(Profile prof : profiles) {
final ProfileFragment profFrag = new ProfileFragment();
profFrag.setName(prof.getName());
profFrag.setGender(prof.getGender());
profFrag.setAge(prof.getAge());
LOGD(TAG, "WELL, ONE PROFILE WAS RETRIEVED");
currView = profFrag.onCreateView(getLayoutInflater(), itLayout, null);
itLayout.addView(currView);//here is my problem
LOGD(TAG, "SUCCESS IN DISPLAY");
}
}
}
How do I reproduce the behaving of stacking the views present in a LinearLayout XML in code?
Before some objections:
I know some of you will say there's a better way to do it, mainly the experient developers (maybe saying I can use ListView with adapters. Yes, I know it, but I didn't reach this part of the guide yet). But, get the fact I'm a very fresh beginner and really want to do this by hand to enjoy the maximum of what a learn.
Change the fragment layout "profile_fragment" height is wrap_content,not fill or match parent.
My english is not well,I hope you can understand ....
R.Layout.profile_fragment:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" // This is your problem.
android:orientation="vertical" >
Related
Problem: I'm receiving a null database name error. The Android Room database calls in my MainActiity.class proceed to execute though there is a prompt to let the user choose the database name.
What I'm trying to do: I'm still learning Android, but I'm trying to work out in a separate app, the use of a master Room database to manage the use of multiple Room databases used by the app (this is a sandbox type of app to play with this idea). This database management functionality is working well but things are hard coded. So, I'm at the step where I'm wanting the user to have the ability, through the use of sharedPreferences and a custom alert prompts, to have the option to create at install the first room database name and add others thereafter. The option to add them later is not an issue as something will be loaded. However, on the intial app launch, I would like the user to have the option to create and name the first DB rather than a default be created -- which is not really that big a deal, but why have a potential default DB the user never uses. I could even develop a method of renaming, which I will, but it seems to make sense to allow the user to do this.
What I have tried: I've tried to create some methods to encapsulate and separate out the DB calls from the prompts, but the code still blows through to the DB code. I did some searching on delaying Room but couldn't find anything specific to it. I'm open to the wisdom of others.
Code again, this is just one activity since I'm playing with the idea... and learning at the same time.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "DB_INFO";
SharedPreferences sharedPreferences;
SharedPreferences.Editor settings;
String databaseName;
String prevDB;
Button button;
MasterDatabase masterDB;
List<MasterDatabaseList> mdbList;
ArrayList<BaseDatabase> bdbList = new ArrayList<>();
// Current Database
int currentBaseDBIndex = -1;
BaseDatabase currentDB = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
sharedPreferences = getSharedPreferences("AppSettings", MODE_PRIVATE);
settings = sharedPreferences.edit();
setupOnClickActions();
startChecks();
Toast.makeText(this, "database: " + databaseName + "\r\n" + "prevDB: " + prevDB, Toast.LENGTH_LONG).show();
}
private void startChecks(){
if(isFirstTime()) {
databaseName = PopupDialog.AlertInputBox(this, "Enter Research Project Name",
"Without spaces or special characters, enter a name for your research project.");
settings.putString("database", databaseName);
settings.commit();
settings.apply();
startDBs();
}else{
databaseName = PopupDialog.AlertInputBox(this, "Enter Research Project Name",
"Without spaces or special characters, enter a new or existing name for your research project.");
settings.putString("prevDB", sharedPreferences.getString("database", ""));
settings.putString("database", databaseName);
settings.commit();
settings.apply();
prevDB = sharedPreferences.getString("prevDB", "");
startDBs();
}
}
private void startDBs(){
masterDB = MasterDatabase.getInstance(this);
mdbList = masterDB.getMasterDao().getAllDatabases();
// Add a DB if none exists
if(mdbList.size()<1){
addBaseDB("sample.db");
}
setCurrentIndexDBandDao(databaseName); /* Add some data to db1 IF it exists (it should) --------------------- */
if (currentBaseDBIndex > -1 && currentDB.getBaseDao().count() < 1) {
currentDB.getBaseDao().insert(new BaseTable("Added " + databaseName + " ... etc."));
}
/* Extract and Log Data for ALL the BaseDatabase databases i.e. db1 and db2 */
for(MasterDatabaseList masterdb: masterDB.getMasterDao().getAllDatabases()) {
Log.d(TAG,"Database is " + masterdb.getDatabaseName());
setCurrentIndexDBandDao(masterdb.getDatabaseName());
if (currentBaseDBIndex > -1) {
for(BaseTable bt: currentDB.getBaseDao().getAllBaseTables()) {
Log.d(TAG,"Extracted Base Table row where MyData is" + bt.getMydata());
}
}
}
}
// METHODS =========================================================================================================
// Attempt to clear and reset SharedPreferences to a user first execution
private void setupOnClickActions(){
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
settings.putBoolean("firstTime", false);
settings.clear();
settings.commit();
settings.apply();
}
});
}
/* Add a new Database
Note that it assumes that it will now be the current
so the current values are set */
private void addBaseDB(String baseDBName) {
masterDB.getMasterDao().insert(new MasterDatabaseList(baseDBName));
}
/* Build/ReBuild the 3 Lists according to the master database*/
private void buildBaseLists() {
bdbList.clear();
mdbList = masterDB.getMasterDao().getAllDatabases();
// Loop through the databases defined in the master database adding the database and dao to the respective lists
for (MasterDatabaseList masterDB: masterDB.getMasterDao().getAllDatabases()) {
BaseDatabase baseDB = BaseDatabase.getInstance(this, masterDB.getDatabaseName());
bdbList.add(baseDB);
}
}
/* Set the currentDB according to the database name*/
private void setCurrentIndexDBandDao(String baseDBName) {
currentBaseDBIndex = getListIndexByBaseDBName(baseDBName);
if(currentBaseDBIndex == -1) {
addBaseDB(baseDBName);
buildBaseLists();
currentBaseDBIndex = getListIndexByBaseDBName(baseDBName);
}
if (currentBaseDBIndex > -1) {
buildBaseLists();
}
currentDB = bdbList.get(currentBaseDBIndex);
}
/* Get the index according to the database name passed*/
private int getListIndexByBaseDBName(String baseDBName) {
if(mdbList==null)
mdbList = masterDB.getMasterDao().getAllDatabases();
int rv = -1; // default to not found
for(int i=0; i < mdbList.size();i++) {
if (mdbList.get(i).getDatabaseName().equals(baseDBName)) {
rv = i;
break;
}
}
return rv;
}
/* Output all rows from the BaseTable for data extracted by the BaseDaos getAllBaseTables */
private void logBaseData(List<BaseTable> baseTableList) {
Log.d(TAG,"Current Database Index is " + currentBaseDBIndex + " DB name is " + mdbList.get(currentBaseDBIndex).getDatabaseName());
for(BaseTable bt: baseTableList) {
Log.d(TAG,"\tMyData value is " + bt.getMydata());
}
}
private boolean isFirstTime(){
if (sharedPreferences.getBoolean("firstTime", true)) {
settings.putBoolean("firstTime", false);
settings.commit();
settings.apply();
return true;
} else {
return false;
}
}
}
The BaseDatabase - databaseName is where the error occurs because of the empty variable
#Database(
entities = {BaseTable.class},
version = 1
)
public abstract class BaseDatabase extends RoomDatabase {
public abstract BaseDao getBaseDao();
private static final int NUMBER_OF_THREADS = 4;
public static final ExecutorService databaseWriteExecutor =
Executors.newFixedThreadPool(NUMBER_OF_THREADS);
public static BaseDatabase getInstance(Context context, String databaseName) {
BaseDatabase instance = null;
if (databaseName != null) {
return Room.databaseBuilder(context, BaseDatabase.class, databaseName)
.allowMainThreadQueries()
.build();
}
return instance;
}
}
Here's an example with 2 activities. The first MainActivity that
displays a list of the available databases (none at first)
clicking on a database allows it to be selected (it is not accessed or created (if new))
allows a database to be added, by entering it's name in the EditText, to the available databases (it is not created or accessed).
allows a second activity to be passed the database name where the activity can then open the database (creating it if it doesn't exist).
note that the database isn't accessed.
So first the MasterDatabaseList class (entity) :-
#Entity(tableName = MasterDatabaseList.TABLE_NAME,
indices = { #Index(value = MasterDatabaseList.COL_DATABASE_NAME, unique = true)
}
)
class MasterDatabaseList {
public static final String TABLE_NAME = "masterdatabaselist";
public static final String COl_ID = "id";
public static final String COL_DATABASE_NAME = "databasename";
public static final String[] ALL_COLUMNS = new String[]{
COl_ID, COL_DATABASE_NAME
};
#PrimaryKey
#ColumnInfo(name = COl_ID)
Long id;
#ColumnInfo(name = COL_DATABASE_NAME)
String databaseName;
public MasterDatabaseList() {}
#Ignore
public MasterDatabaseList(String databaseName) {
this.databaseName = databaseName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDatabaseName() {
return databaseName;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
}
similar to previous answer (but note some added constants)
MasterDatabaseDao
#Dao
abstract class MasterDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(MasterDatabaseList masterDatabaseList);
#Query("SELECT * FROM masterdatabaselist")
abstract List<MasterDatabaseList> getAllDatabases();
Cursor getAllDatabasesAsCursor() {
MatrixCursor matrixCursor = new MatrixCursor(
new String[]{
BaseColumns._ID, /* Cursor Adapter must use _id column for id) */
MasterDatabaseList.COL_DATABASE_NAME
},
0
);
for(MasterDatabaseList m: getAllDatabases()) {
matrixCursor.addRow(new Object[]{m.id,m.databaseName});
}
return matrixCursor;
}
}
Note the new method that gets the list of available databases as a Cursor (for the ListView)
MasterDatabase
#Database(
entities = {MasterDatabaseList.class},
version = 1
)
abstract class MasterDatabase extends RoomDatabase {
abstract MasterDao getMasterDao();
static volatile MasterDatabase instance = null;
public static MasterDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context,MasterDatabase.class,"master.db")
.allowMainThreadQueries()
.build();
}
return instance;
}
}
unchanged from previous answer
2nd Activity UseSelectedDatabase
public class UseSelectedDatabase extends AppCompatActivity {
public static final String INTENT_EXTRA_DATABASEID = "database_id";
public static final String INTENT_EXTRA_DATABASENAME = "database_name";
long mDatabaseId;
String mDatabaseName;
TextView mDatabaseBeingUsed;
Button mDoneButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_use_selected_database);
mDatabaseBeingUsed = this.findViewById(R.id.database_name);
mDoneButton = this.findViewById(R.id.done);
mDatabaseId = this.getIntent().getLongExtra(INTENT_EXTRA_DATABASEID,-1);
mDatabaseName = this.getIntent().getStringExtra(INTENT_EXTRA_DATABASENAME);
mDatabaseBeingUsed.setText(mDatabaseName);
setDoneButton();
/*
can now get an instance of the database
*/
}
private void setDoneButton() {
mDoneButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
finish();
}
});
}
}
This doesn't access the database but just receives the database name and id in the master database. i.e. just shows that you can pass all the information required to access the database.
2nd Activities layout activity_use_selected_database.xml :-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".UseSelectedDatabase">
<TextView
android:id="#+id/database_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="No Database Set?"
>
</TextView>
<Button
android:id="#+id/done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DONE">
</Button>
</LinearLayout>
Initial Activity MainActivity :-
public class MainActivity extends AppCompatActivity {
MasterDatabase mMasterDB;
MasterDao mMasterDBDao;
EditText mDBToAdd;
Button mAddDB,mUseSelectedDatabase;
ListView mDatabaseList;
SimpleCursorAdapter mSCA;
Cursor mCsr;
long mSelectedDatabaseId = 0;
String mSelectedDatabaseName = "";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDBToAdd = this.findViewById(R.id.database_name);
mAddDB = this.findViewById(R.id.addDatabase);
mUseSelectedDatabase = this.findViewById(R.id.useSelectedDatabase);
mDatabaseList = this.findViewById(R.id.database_list);
mMasterDB = MasterDatabase.getInstance(this);
mMasterDBDao = mMasterDB.getMasterDao();
setUpAddDBButton();
setUpUseSelectedDatabaseButton();
setOrRefreshDatabaseList();
}
private void setUpAddDBButton() {
mAddDB.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mDBToAdd.getText().toString().length() > 0) {
if (mMasterDBDao.insert(new MasterDatabaseList(mDBToAdd.getText().toString())) > 0) {
mDBToAdd.setText("");
setOrRefreshDatabaseList();
}
}
}
});
}
private void setUpUseSelectedDatabaseButton() {
mUseSelectedDatabase.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mSelectedDatabaseId > 0) {
Intent intent = new Intent(view.getContext(),UseSelectedDatabase.class);
intent.putExtra(UseSelectedDatabase.INTENT_EXTRA_DATABASEID, mSelectedDatabaseId);
intent.putExtra(UseSelectedDatabase.INTENT_EXTRA_DATABASENAME,mSelectedDatabaseName);
startActivity(intent);
}
}
});
}
private void setOrRefreshDatabaseList() {
mCsr = mMasterDBDao.getAllDatabasesAsCursor();
if (mSCA == null) {
mSCA = new SimpleCursorAdapter(
this.getApplicationContext(),
android.R.layout.simple_list_item_1,
mCsr,
new String[]{MasterDatabaseList.COL_DATABASE_NAME},
new int[]{android.R.id.text1},
0
);
mDatabaseList.setAdapter(mSCA);
mDatabaseList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
/* Handle Clicking on an Item (i.e. prepare UseSelected Button) */
#SuppressLint("Range")
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
mSelectedDatabaseId = l;
if (l > 0) {
mSelectedDatabaseName = mCsr.getString(mCsr.getColumnIndex(MasterDatabaseList.COL_DATABASE_NAME));
mUseSelectedDatabase.setText(mSelectedDatabaseName);
mUseSelectedDatabase.setClickable(true);
} else {
mUseSelectedDatabase.setText("NO DATEBASE SELECTED");
mUseSelectedDatabase.setClickable(false);
}
}
});
} else {
mSCA.swapCursor(mCsr);
}
}
#Override
protected void onResume() {
super.onResume();
setOrRefreshDatabaseList();
}
#Override
protected void onDestroy() {
super.onDestroy();
mCsr.close();
}
}
Initial Activity's layout activity_main.xml :-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<EditText
android:id="#+id/database_name"
android:layout_width="500dp"
android:layout_height="wrap_content"
android:text="">
</EditText>
<Button
android:id="#+id/addDatabase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Database"
>
</Button>
<ListView
android:id="#+id/database_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ListView>
<Button
android:id="#+id/useSelectedDatabase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="NO SELECTED DATABASE"
android:clickable="false"
>
</Button>
</LinearLayout>
Demo
When first run MainActivity displays :-
i.e. no available databases (clicking buttons does nothing).
Database Test001 entered into Edit Text and then Add Database clicked :-
at this stage the Test001 database has not been created. However the MasterDatabase has been created and the Test001 row has been added (i.e. at some time the database could be opened and created if it doesn't already exist) :-
Test001 is clicked :-
The Use Selected button has been changed to Test001 (perhaps should be Use Test001).
Still the Test001 database hasn't been accessed at all, as there is no need.
Database Test002 is entered into the Edit Text (cleared after adding) and Add Database is clicked :-
Note if an existing database is entered into the Edit Text it will NOT be added and the Text Box will not be cleared.
Test002 is clicked, the button changes to Test002 and the button is clicked, starting the 2nd activity :-
The database isn't opened or accessed (i.e. further demonstrating waiting and doing things)
The DONE button is clicked returning to the first activity. Still the database has not been opened/accessed BUT with the other code (Base???? clases etc) could be (probably in the UseSelectedDatabase activity).
Extra As proof of concept the the BaseTable??? classes from the previous question were added, as were some new User??? classes with BaseDatabase being :-
#Database(
entities = {BaseTable.class,User.class},
version = 1
)
abstract class BaseDatabase extends RoomDatabase {
abstract BaseDao getBaseDao();
abstract UserDao getUserDao();
public static BaseDatabase getInstance(Context context, String databaseName) {
BaseDatabase db = Room.databaseBuilder(context, BaseDatabase.class, databaseName)
.allowMainThreadQueries()
.build();
db.getOpenHelper().getWritableDatabase();
return db;
}
}
With the following added to the UseSelectedDatabase activity:-
....
/*
can now get an instance of the database
*/
db = BaseDatabase.getInstance(this,mDatabaseName);
baseDao = db.getBaseDao();
userDao = db.getUserDao();
baseDao.insert(new BaseTable("X"));
userDao.insert(new User("Snowman","Fred","Wlibur","Bloggs","password",0));
List<BaseTable> baseTableList = baseDao.getAllBaseTables();
List<User> userList = userDao.getAllUsers();
Restarting the App selecting Test001 and then Test002 results in the database being shown as :-
i.e. Test001, albeit it closed, has obviously been created as has Test002.
I am using PagingDataAdapter to build my recyclerview but somehow in the mainactivity android studio shows an error that submitData is not a function of the respective adapter.
this is my code for my in the MainActivity:
viewModel = new ViewModelProvider(this ,
ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication()))
.get(PhotoDataSourceFactory.class);
Lifecycle lifecycle = getLifecycle();
viewModel.setLifecycle(lifecycle);
recyclerView = findViewById(R.id.recyclerView);
PhotosAdapter adapter = new PhotosAdapter(Photos.CALLBACK);
recyclerView.setAdapter(adapter);
viewModel.getSearchResults().observe(this, listPagingData -> adapter.submitData(lifecycle, listPagingData));
My ViewModel :
public class PhotoDataSourceFactory extends ViewModel {
Lifecycle lifecycle;
private static final String TAG = "PhotoDataSourceFactory";
public PhotoDataSourceFactory() {
}
public void setLifecycle(Lifecycle lifecycle){
this.lifecycle = lifecycle;
}
public LiveData<PagingData<List<Photos>>> getSearchResults(){
PagingConfig config = new PagingConfig(20, 100, false);
Pager<Long , List<Photos>> pager = new Pager<>(config, new Function0<PagingSource<Long, List<Photos>>>() {
#Override
public PagingSource<Long, List<Photos>> invoke() {
return new PhotoSource();
}
});
return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager),lifecycle);
}
}
My Adapter:
public class PhotosAdapter extends PagingDataAdapter<Photos , PhotosAdapter.PhotoViewHolder> {
private static final String TAG = "PhotosAdapter";
public PhotosAdapter(#NotNull DiffUtil.ItemCallback<Photos> diffCallback) {
super(diffCallback);
}
#NonNull
#Override
public PhotoViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.list_photos,parent,false);
return new PhotoViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull PhotoViewHolder holder, int position) {
Glide.with(holder.itemView.getContext()).load(getItem(position).getUrl()+".png").into(holder.photo);
holder.textView.setText(getItem(position).getTitle());
}
public class PhotoViewHolder extends RecyclerView.ViewHolder{
ImageView photo;
TextView textView;
public PhotoViewHolder(#NonNull View itemView) {
super(itemView);
photo = itemView.findViewById(R.id.imageView);
textView = itemView.findViewById(R.id.textView);
}
}
}
and My PagingSource:
public class PhotoSource extends ListenableFuturePagingSource<Long , List<Photos>> {
GetDataService dataService;
private static final String TAG = "PhotoSource";
#NotNull
#Override
public ListenableFuture<LoadResult<Long, List<Photos>>> loadFuture(#NotNull LoadParams<Long> loadParams) {
Executor executor = Executors.newSingleThreadExecutor();
dataService = RetrofitInstance.getInstance().create(GetDataService.class);
Long currentPage = loadParams.getKey()!=null ? loadParams.getKey() : (long)1;
ListenableFuture<List<Photos>> photos = dataService.getAllPhotos(currentPage);
ListenableFuture<LoadResult<Long , List<Photos>>> page = Futures.transform(photos, new Function<List<Photos>, LoadResult<Long, List<Photos>>>() {
#NullableDecl
#Override
public LoadResult.Page<Long, List<Photos>> apply(#NullableDecl List<Photos> input) {
return new LoadResult.Page<>(
Collections.singletonList(input),
currentPage == 1 ? currentPage : currentPage-1,
input.isEmpty() ? null : currentPage+1
);
}}, executor);
Log.d(TAG, "loadFuture: Heererereree");
ListenableFuture<LoadResult<Long , List<Photos>>> partialLoad = Futures.catching(page, HttpException.class, LoadResult.Error::new, executor);
return Futures.catching(partialLoad, IOException.class, LoadResult.Error::new, executor);
}
#Nullable
#Override
public Long getRefreshKey(#NotNull PagingState<Long, List<Photos>> pagingState) {
return null;
}
}
I don't understand where i am going wrong , tried to search alot on stackoverflow but all in vain ,
I wasn't able to glean anything of substance from google's documentation on paging 3 in java to resolve this issue. tried to find videos on paging 3 in java but unfortunately no one has covered paging in Java.
if there's anyone who has done this (paging 3) in java please if possible add a link to your github too
thanks in advance :)
I had encountered a similar issue. In my case, the problem was not with the "adapter.submitData()" but with the actual response itself. The response from the api had issues because of me using suspend function for getting the response data. So, please check the response from your api for debugging.
Just found out what went wrong ,check the code below if you want a basic overview of paging 3 in java.
https://github.com/manas-droid/Android-Paging-3/tree/main/app/src/main/java/com/example/photos
I have this arraylist:
List<View> mListViews = new ArrayList<View>();
which is populated by
addView(mListViews, "file:///android_asset/a.html");
addView(mListViews, "file:///android_asset/b.html");
addView(mListViews, "file:///android_asset/c.html");
and the addView method:
private void addView(List<View> viewList,String url) {
WebView webView=new WebView(this);
webView.loadUrl(url);
viewList.add(webView);
}
I would like to get the element from the second position in the arraylist as a string. So the expected result is file:///android_asset/b.html. I tried
String test = mListViews.get(1);
But I received error incompatible type, required java.lang.String, found android.view.View.
How to solve this?
If this was my issue, I would do the following:
Create a model class...
public class ListModel {
View view;
String url;
public ListModel (View view, String url) {
this.view = view;
this.url = url;
}
public View getView() {
return view;
}
public void setView(View view) {
this.view = view;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
Make a list that takes in that class...
public class MainActivity extends AppCompatActivity {
List<ListModel> listModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listModel = new ArrayList<>();
listModel.add(new ListModel(view, "url"));
}
}
From there you can create a for loop to get the view and / or the url..
for (int i = 0; i < listModel.size(); i++) {
View getView = listModel.get(i).getView();
String getUrl = listModel.get(i).getUrl();
}
Now, keep in mind, my code will NOT work because I didn't create a VIEW to put into the list. But this is probably the best way to go about it.
Use this:
String test = ( (WebView) mListViews.get(1) ).getUrl();
Be aware that if you do this before a page has finished loading, test will be null.
Another option would be to create a class encapsulating a WebView and the url string, then creating a list of objects of that class.
As you write you have ArrayList of View type.
Result of mListViews.get(1) is type of View. You can't assign View to String variable.
Question is for what you creating ArrayList<View> and populate it with WebView?
If you need the url from the webView I think you just need to do this:
String test = mListViews.get(1).getURL();
I am using a ListView to display some JSON data and want to display each result according to its type (Artist, Release, Label...).
I will be using an interface implemented by each type of result :
public interface Result {
public Int getId();
public String getThumb();
// ...
}
I would like to know which of these choices is the best solution (I am open to better things, that's just what I had on the top of my head):
creating an enum ResultType in the interace (so inherited class will have to return their own value like ResultType.ARTIST in a getType() method
checking the instance type using isInstance()
I would like to know what would be the best way to perform something equivalent to this C code (array of function pointer) as I would like to avoid using to many if/else statements.
typedef struct s_func {
const char *type_name;
void* (*func_pointer)(void *result_infos);
} t_func;
static t_func type_array[] = {
{"artist", artist_function},
{"label", label_function},
// ....
{NULL, NULL}
}
void check_type(const char *type_string)
{
int i, j = 0;
char *key_value;
// compare string and array key
while (type_array && type_array[i][0]) {
key_value = type_array[i][0];
// if key match
if (type_string && strncmp(type_string, key_value, strlen(type_string)) == 0) {
type_array[i][1](); // call appropriate function;
}
i++;
}
}
I guess it would be using a HashMap but (I might be wrong) it doesn't seem to have a litteral notation. Is there any easy way to build an HashMap of pairs ?
Thank you
I think you can use an ArrayAdapter.
Take a look at this tutorial to see what I mean.
It'll need some twiddling so that it can deal with the different kinds of items.
Make an interface MyListItem
public interface MyListItem {
public int getLayout();
public void bindToView(View v);
}
Make different layouts for the display of Artist, Release, Label.
Make classes Artist, Release, Label that implement MyListItem.
public class Artist implements MyListItem {
private String Name;
public Artist(String name){
this.name = name;
}
public int getLayout() {
return R.layout.artistlayout;
}
public void bindToView(View v) {
TextView textView = (TextView) rowView.findViewById(R.id.artistLabel);
textView.setText(name);
}
}
Now the adapter only has to call the right methods to fill the view for the selected item.
public class MySimpleArrayAdapter extends ArrayAdapter<MyListItem> {
private final Context context;
private final MyListItem[] values;
public MySimpleArrayAdapter(Context context, MyListItem[] values) {
super(context, android.R.layout.simple_list_item_1, values);
this.context = context;
this.values = values;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
MyListItem item = values[position];
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(item.getLayout(), parent, false);
item.bindTo(view);
return view;
}
}
I am new to Android/Java programming. I have two classes, one is an Activity and the other is a normal class. My activity class contains a TextView. Can I update the TextView of the activity class from a normal class? I tried with random code, but it fails.
// activity class
public class MainMenu extends Activity {
public TextView txtView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView txtView = (TextView)findViewById(R.id.text);
}
}
// Other class
public class ClassB {
public ClassB() {
public void Update() {
TextView txtView = (TextView)findViewById(R.id.text);
txtView.setText("Hello");
}
}
}
You have to pass the Context reference via constructor.
public class ClassB {
Context context;
public ClassB(Context context){
this.context=context;
}
public void Update(){
TextView txtView = (TextView) ((Activity)context).findViewById(R.id.text);
txtView.setText("Hello");
}
The preceding two examples require TextView to be used directly within the other class. However, there are cases where TextView shouldn't be present in the other class, e.g., your ClassB is used to update various Activities, where some activities update TextViews, and others might update EditTexts.
Hence, the below solution can guide you on how you could decouple your TextView from other classes, yet, you could still achieve what you want. It's using the interface approach.
Firstly, declare an interface where you could have ClassB communicate to the Activity, and call it MyCallback:
public interface MyCallback {
// Declaration of the template function for the interface
public void updateMyText(String myString);
}
Next in your Activity, implement MyCallback, and hence its function definition. In this function, you will receive the String from ClassB that you could do whatever you like, e.g., update the TextView (or EditText, etc.):
public class MyActivity extends AppCompatActivity implements MyCallback {
// ... whatever code of your activity
#Override
public void updateMyText(String myString) {
((TextView)findViewById(R.id.text)).setText(myString);
}
}
Lastly, you could declare ClassB that takes in MyCallback (i.e., your Activity class object that is also a MyCallback). From there you could use ClassB to communicate back to Activity and get it to update its TextView through the updateMyText function:
public class ClassB {
MyCallback myCallback = null;
public ClassB(MyCallback callback) {
this.myCallback = callback;
}
public void doSomething() {
// Do something to get String
String myString = str;
if (myCallback != null) {
myCallback.updateMyText(myString);
}
}
}
Hope this helps better show the architected structure of decoupling the Activity properly from ClassB.
This is actually a deceptively "simple" question, but in reality a complicated problem in the context of Android development.
Activities are the "process entry point", meaning that any Activity you see can act as the "first point of entry to your application on start-up". People think that only the Activity that has the MAIN/LAUNCHER intent filter can be launched at start-up, but this is false.
Any Activity can act as the "first Activity", because Android can restart it from any point with the current active navigation stack.
Anyways, with that in mind, an Activity can show a View, and people often use the Activity to hold each screen of their app (instead of using it as an entry point, and swapping out view controllers in it ~ fragments).
So if you have multiple Activities, then you need to share data between them in such a way, that you take it into consideration that both activities can be started up at any time as the first Activity of the app.
For this, what you need to do is not "set the text view's text directly from another class", but you need to modify observable shared data.
The newly released official Android Architecture Components provide the LiveData<T> class, which has a subclass called MutableLiveData<T>.
To update the data from one class to another Activity, what you must do is have a global data exposed as a LiveData
public class MyApplication extends Application {
private static MyApplication INSTANCE;
DataRepository dataRepository; // this is YOUR class
#Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
dataRepository = new DataRepository();
}
public static MyApplication get() {
return INSTANCE;
}
}
The DataRepository should expose LiveData:
public class DataRepository {
private final MutableLiveData<MyData> data = new MutableLiveData<>();
public LiveData<MyData> getMyData() {
return data;
}
public void updateText(String text) {
MyData newData = data.getValue()
.toBuilder() // immutable new copy
.setText(text)
.build();
data.setValue(newData);
}
}
Where the Activity subscribes to this:
public class MyActivity extends BaseActivity {
DataRepository dataRepository;
TextView textView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication app = (MyApplication)getApplicationContext();
dataRepository = app.getDataRepository();
setContentView(R.layout.main_activity);
textView = findViewById(R.id.textview);
dataRepository.getMyData().observe(this, new Observer() {
#Override
public void onChange(MyObject myObject) {
textView.setText(myObject.getText());
}
}
}
So to update this text, you need to get the DataRepository class, and call updateText on it:
DataRepository dataRepository = MyApplication.get().dataRepository();
dataRepository.updateText("my new text");
And this will properly update your Activity text view.
Beware that you should also persist the data to onSaveInstanceState(Bundle so that it is not lost (assuming the data is not from disk).
To do that, you need to do the following:
public class BaseActivity extends AppCompatActivity {
DataRepository dataRepository;
private static boolean didRestoreGlobals = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
MyApplication app = (MyApplication)getApplicationContext();
dataRepository = app.getDataRepository();
super.onCreate(savedInstanceState);
if(!didRestoreGlobals) {
didRestoreGlobals = true;
if(savedInstanceState != null) {
dataRepository.restoreState(savedInstanceState.getBundle("dataRepository"));
}
}
}
#Override
protected void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
bundle.putBundle("dataRepository", dataRepository.saveState());
}
}
And then add saveState/restoreState methods to DataRepository accordingly.
If you are creating an object of your other class(ClassB)inside activity class, the simplest solution is to pass the TextView through constructor (if you aren't create an object in the activity class this answer will not be helpful). So your example should be like below:
// activity class
public class MainMenu extends Activity {
public TextView txtView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
txtView = (TextView)findViewById(R.id.text);
//instantiating a object of the ClassB and passing tv
ClassB obj = new ClassB(txtView);
}
}
// other class
public class ClassB {
//declarre tv
TextView txtView;
//get the tv as arg
public ClassB(TextView tv){
txtView = tv;
}
public void Update(){
txtView.setText("Hello");
}
}
You can make a getter method in your Activity.
In your Activity class:
public TextView getTextView()
{
TextView txtView = (TextView)findViewById(R.id.text);
return txtView;
}
In your ClassB class:
public void Update()
{
MainMenu obj = new MainMenu();
TextView tv = obj.getTextView();
tv.setText("hello");
}
I have a XML page (Bert.XML) with four TextViews with ID's TextView1id, TextView2id, TextView3id and TextView4id
<GridLayout
android:id = "#+id/gridLayout"
android:layout_width = "match_parent"
android:layout_height = "wrap_content"
android:paddingTop="10dp">
<TextView
android:id = "#+id/TextView1id"
android:layout_gravity="end"
android:hint = "#+id/Risico"
android:textSize="#dimen/edit_size"
android:layout_height = "wrap_content"
android:layout_width = "fill_parent"
android:layout_column = "0"
android:layout_row = "1"
android:layout_columnSpan = "3"
/>
<TextView
android:id = "#+id/TextView2id"
android:layout_gravity="end"
android:hint = "#+id/Risico"
android:textSize="#dimen/edit_size"
android:layout_height = "wrap_content"
android:layout_width = "fill_parent"
android:layout_column = "0"
android:layout_row = "2"
android:layout_columnSpan = "3"
/>
<TextView
android:id = "#+id/TextView3id"
android:layout_gravity="end"
android:hint = "#+id/Risico"
android:textSize="#dimen/edit_size"
android:layout_height = "wrap_content"
android:layout_width = "fill_parent"
android:layout_column = "0"
android:layout_row = "3"
android:layout_columnSpan = "3"
/>
<TextView
android:id = "#+id/TextView4id"
android:layout_gravity="end"
android:hint = "#+id/Risico"
android:textSize="#dimen/edit_size"
android:layout_height = "wrap_content"
android:layout_width = "fill_parent"
android:layout_column = "0"
android:layout_row = "4"
android:layout_columnSpan = "3"
/>
</GridLayout>
The code for this view is shown below. In here I change the text of the TextViews through the Mail Class. The Activity has been given as a parameter for the Mail Class
package nl.yentel.finekinney;
import android.app.Activity;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.TextView;
public class Bert extends AppCompatActivity {
private TextView theObject;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bert);
//both findViewByID work
theObject = this.findViewById(R.id.TextView2id);
theObject = findViewById(R.id.TextView2id);
Mail theMail=new Mail();
theMail.activity=this;
theMail.NameOfObject="TextView2id";
theMail.KindOfObject="TextView";
theMail.Mail();
CalculateFromClass(this);
Calculate(this);
}
//Calculate(dezeActiviteit);
public void Calculate(Activity dezeActiviteit) {
//here you should include dezeActiviteit which can be called from the Class
theObject = dezeActiviteit.findViewById(R.id.TextView1id);
theObject.setText("text from method");
}
public void CalculateFromClass(Activity dezeActiviteit) {
//here you should include dezeActiviteit which can be called from the Class
theObject = dezeActiviteit.findViewById(R.id.TextView4id);
theObject.setText("text from Class");
}
}
My Mail Class looks like this
package nl.yentel.finekinney;
import android.app.Activity;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class Mail extends AppCompatActivity {
public String NameOfObject;
public String KindOfObject;
public Activity activity;
void Mail() {
//if the name and kind has been given as an input parameter
int ressourceId = activity.getResources().getIdentifier(NameOfObject, "id", activity.getPackageName());
if (KindOfObject.equals("TextView")) {
TextView TextViewObject = activity.findViewById(ressourceId); //VISArB 14
TextViewObject.setText("this is a TextView");
}
if (KindOfObject.equals("EditText")) {
EditText EditTextObject = activity.findViewById(ressourceId); //VISArB 14
EditTextObject.setText("this is an EditText");
}
//if the name is hard coded
TextView TextViewObject;
TextViewObject = activity.findViewById(R.id.TextView3id);
TextViewObject.setText("Hard coded ID");
//if I want to run a method from my main Class
Bert dezeBert = new Bert();
dezeBert.CalculateFromClass(activity);
}
}
This is kotlin code to access the view inside another layout :
//inflate layout
val view = inflate(this, R.layout.ly_custom_menu_item, null)
//access view inside the inflated
val tv = view.findViewById<AppCompatTextView>(R.id.txtV_WalletBalance_SideMenu)
//set value to view
tv.text = "Value"
//Add inflated Layout to something
you can do the following thing. I tried it and it worked.
Just pass in the reference to the TextView while calling the method from another class.
The problem in your version, is that there is conflict between TextView because you are declaring it twice.
Instead declare it only once and pass it as an argument while calling the method in another class.
Cheers!!
// activity class
public class MainMenu extends Activity {
public TextView txtView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView txtView = (TextView)findViewById(R.id.text);
ClassB.update(txtView);
}
}
// other class
public class ClassB {
public ClassB(){
}
public void update(TextView tv){
tv.setText("Hello");
}
}
This can be manage easily in simple to steps.
================================
1) Activity to Multiple Fragment
below line can write in Fragment class Via FindViewById
((TextView) ((Activity) getActivity()).findViewById(R.id.textview)).setText("");