How to access Room database from different fragments? - java

In my app I have a TabLayout and each of the tabs is represented by a fragment. I have several tables in a database. And for each table I want to have a tab that would display a list of table's contents. To access a database I need to pass in a context but it's only available from the MainActivity. How to access a database instance from each fragment?
Here's some code:
ElectronicsDatabase.java
#Database(entities = {Smartphone.class, Tablet.class,
Laptop.class, VideoGameConsole.class}, version = 1)
public abstract class ElectronicsDatabase extends RoomDatabase {
public abstract SmartphoneDao getSmartphoneDao();
public abstract TabletDao getTabletDao();
public abstract LaptopDao getLaptopDao();
public abstract VideoGameConsoleDao getVideoGameConsoleDao();
private static final String DB_NAME = "products.db";
private static ElectronicsDatabase db;
public static ElectronicsDatabase getInstance(Context context)
{
if (db == null)
{
db =buildDatabaseInstance(context);
}
return db;
}
private static ElectronicsDatabase buildDatabaseInstance(Context context)
{
return Room.databaseBuilder(context, ElectronicsDatabase.class,
DB_NAME).allowMainThreadQueries().build();
}
}
And in the main activity I access it like this:
db = ElectronicsDatabase
.getInstance(getApplicationContext());

In your fragments you can use getActivity() to acccess context of your parent activity.
but i suggest you to use viewModel for accessing to your database.

Try to use ViewModel to access database in any activity or Fragment
ViewModel is a class that is responsible for preparing and managing the data for a UI component (activity or Fragment)

In your case which is needing a Context for DB access via Room, it is better to pass a non-UI Context as to avoid unnecessary information being passed around for no reason leading to possible memory leaks.
You can get access to a non-UI Context which will be called ApplicationContext from your base activity, or your main activity. Simply like this:
Context appContext= getApplicationContext();
Then store it in a Repo class, so you can simply use it anytime you need it again anywhere without worrying about it.
However, if you need a context for something related to drawing on the screen, like inflating an XML for example, then in that case you will need a UI Context as not to lose UI details like your theme for example. In that case you can get the context from inside your fragment using:
getContext() Or getActivity().
I won't go to further details about contexts but,
if you want to learn more about what Context really is you can start from here:
https://medium.freecodecamp.org/mastering-android-context-7055c8478a22

That might be a very late answer, but I believe it might help some in the future:
If you are using multiple fragments and DI, you can create a viewModel for your activity, then inject the desired value into it
#HiltViewModel
class MainActivityViewModel #Inject constructor(
private val repository: PlantsRepository
): ViewModel() {
private val mutableLiveData = MutableLiveData<List<Plant>>()
val liveData: LiveData<List<Plant>> = mutableLiveData // object to observe
init {
viewModelScope.launch {
mutableLiveData.postValue(repository.getAllPlants())
fillExampleData()
}
}
}
(sample for reference)
then you simply use it in your fragments:
class FragmentExampleScreen : Fragment() {
private val sharedViewModel: MainActivityViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
...
sharedViewModel.liveData.observe(viewLifecycleOwner) { idk ->
Log.i("hello", idk.toString())
}
...
}
}

Related

Getting null value in fragment spinner

I am trying to use filter in Fragment and implementing the dialog fragment.
This is the class that I am using
public class HomeFragment extends Fragment implements
FilterDialogFragment.FilterListener,
PostAdapter2.OnPostSelectedListener{ detail code }
this the dialogfragment based class for spinner choosing options
public class FilterDialogFragment extends DialogFragment
this method is called upon clicking the filter button, which pops up dialog for spinner options of the filter
Declared
private FilterDialogFragment mFilterDialog;
in onCreateView
mFilterDialog = new FilterDialogFragment();
Method to call
public void onFilterClicked(){
mFilterDialog.show(getSupportFragmentManager(), FilterDialogFragment.TAG);
}
after this upon selecting the spinner option and clicking apply this method is called in which mFilterListener is null which should not be the case
public interface FilterListener {
void onFilter(Filters filters);
}
FilterListener mFilterListener;
public void onSearchClicked() {
Log.d("Message", String.valueOf(mFilterListener));
if (mFilterListener != null) {
Log.d("Message", "New 55555");
mFilterListener.onFilter(getFilters());
}
dismiss();
}
please assist me to solve this problem. if anymore details are required please let me know
attach method in FilterDialogFragement
public void onAttach(Context context) {
super.onAttach(context);
Log.d("Message", "New 6666666");
Log.d("Message", String.valueOf(mFilterListener));
if (context instanceof FilterListener) {
// Log.d("Message", String.valueOf(mFilterListener));
mFilterListener = (FilterListener) context;
}
}
You are attempting to mimic this library implementation: Friendly Eats.
However, you do not copy it wholesale, mainly in that you choose to use HomeFragment which implements FilterDialogFragment.FilterListener to launch FilterDialogFragment, rather than the library's MainActivity. This is the cause of your null pointer.
This is due to how getSupportFragmentManager() works. If you look at Android's documentation for this, you will see it says
Return the FragmentManager for interacting with fragments associated with this activity. (My Bolding)
When you call mFilterDialog.show(getSupportFragmentManager(), FilterDialogFragment.TAG); inside HomeFragment, you are actually calling whatever Activity that is the parent of HomeFragment to launch the new FilterDialogFragment. You could double check this by checking if, in onAttach(Context context) inside HomeFragment, if context instanceof HomeFragment. I do not think it will return true.
To solve this, and without changing your use of HomeFragment, you could simply pass an instance of HomeFragment itself, or a separate implementation of FilterDialogFragment.FilterListener (which I'd prefer if you do not need to use anything from HomeFragment other than the listener) to your FilterDialogFragment instance on creation or before you launch it.
For example, you could create a public setter like so:
private FilterListener mFilterListener;
public void setFilterListener(FilterListener filterListener){
mFilterListener = filterListener;
}
and then in your HomeFragment onCreateView(), you do this:
mFilterDialog = new FilterDialogFragment();
//Or preferably, an anonymous/named implementing instance of the interface only.
mFilterDialog.setFilterListener(this);
Doing so would not rely on the Android framework to provide the initialisation of your field, and does not require you to either change your Activity or HomeFragment you are currently using.
I assume, that u didnt set the listener in a mFilterDialog, so thats why its null

How to get access to view elements from adapter kotlin?

I'm moving from java to kotlin and I faced with some difficulties which are connected with static method usage at kotlin. I'd like to get access from recyclerView adapter to views at my activity. At Java I did smth like that at adapter:
WriteResponseMess.deleteAttachment(position);
and static method at my activity:
public static void deleteAttachment(int adapterPosition) {
mNames = adapter.getItems();
mNames.remove(adapterPosition);
adapter.updateNames(mNames);
adapter.notifyDataSetChanged();
mNames = adapter.getItems();
}
right now I want to do it via kotlin. The main tack is that I have to delete item of RV and update views. I have read this and this resources and I have done smth like that:
companion object {
#JvmStatic
fun deleteAttachment(position: Int) {
}
}
but I don't have any access to activity variables, so what I have to do in that situation?
A static block can access only static members.
The activity member variables should also be a part of the companion object.
For instance :-
companion object {
var mNames : MutableList<Name> = mutableListOf // is a member variable
......
fun deleteAttachment(position: Int) {
}
}

Can an Android Context inside an activity be static?

I have a static function in which I need to access my color resources. In order access color resources I need context variable which is static. I am confused if I can make context static. Is there any side effects to it? or, is there any other way I can access my resources without using context
Here is the function
private static SpannableStringBuilder setTextColor(
SpannableStringBuilder Text, int spanLength, boolean isSuggestion) {
addressText.setSpan(
new ForegroundColorSpan(
context
.getResources()
.getColor(
isSuggestion ? R.color.blur: R.color.red)),
addressText.length() - 1 - spanLength,
addressText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return Text;
}
I am confused if I can make context static. Is there any side effects
to it?
You can declare a context as static but it is not recommended in Android, because it might lead to a memory leak in your app.
is there any other way I can access my resources without using
context?
No, you need a context instance to access resources in your app.
Back to your case, the easiest way is passing a context as param of the method.
private static SpannableStringBuilder setTextColor(Context context, SpannableStringBuilder Text, int spanLength, boolean isSuggestion) {
int color = context.getResources().getColor(isSuggestion ? R.color.blur : R.color.red);
addressText.setSpan(new ForegroundColorSpan(color),
addressText.length() - 1 - spanLength,
addressText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return Text;
}
Inside your activity, pass this as context when calling setTextColor, for example.
setTextColor(this, new SpannableStringBuilder(), 0, false);
In Kotlin, you can achieve this by creating a class that extends Application and storing the application context in the companion object.
Usually it will look similar to this:
class App : Application() {
override fun onCreate() {
super.onCreate()
instance = this
}
companion object {
private var instance: App? = null
val context: Context?
get() = instance?.applicationContext
}
}
And you can access the context anywhere via App.context
In regards to your concerns about storing a static context, if you were storing an activity or fragment context then you risk creating memory leaks, but since we're storing the application context which is tied to the lifecycle of the entire application, there won't be any issues with memory leaks.
You may run into issues if you want to write testable code that depends on a static context, in which case I would recommend you pass the context into the function rather than access it directly.
1) Create your App class which extend Application
private static Context context;
#Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
2) Create getApplicationContext() method which return context
public static Context getApplicationContext() {
return context;
}
3) Now you can get context anywhere in your class like
Context context = App.getContext().getApplicationContext();

How to create a reusable RecyclerView using "Composition over Inheritance"?

One of my coworkers created an inherit from a RecyclerView and added the logic to create its adapter, defined custom list item attributes, and layout manager inside it.
This is an example of his idea:
class CustomRecyclerView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
init {
loadCustomAttributes()
setDefaultAdapter()
setDefaultLayoutManager()
}
private fun loadCustomAttributes() {
// Load custom attributes: item background, for example.
}
private fun setDefaultAdapter() {
// Define specific Custom Adapter.
}
private fun setDefaultLayoutManager() {
// Define specific Layout Manager.
}
fun setData(data: List) {
// Set data and notify dataset changed.
}
private class CustomAdapter : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
// Specific Adapter.
}
private class ViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
// Specific View Holder.
}
}
I understand his reasons, and it's very reasonable: he wants to plug a View on the XML and just reuse it, without defining the adapter or anything else on the Activity or Fragment; he will just set the data with the method "setData".
But on the other way, I feel that this class is inheriting a RecyclerView just to breaking the design of the RecyclerView and giving too many responsibilities to it. My pain points are:
A RecyclerView shouldn't decide an item attribute (item background colour, for example);
A RecyclerView shouldn’t know about his LayoutManager;
A RecyclerView shouldn't have methods to update the data on the adapter;
A RecyclerView shouldn’t create an instance of his Adapter.
This is an "inheritance over composition" approach.
I tried to find information about it on the internet, but I didn't find anything about good practices when inheriting from a ListView/RecyclerView.
Another idea would be to wrap it inside another layout (ViewGroup or FrameLayout) to encapsulate it. But this would create an unnecessary nested layout, and it will be complicated to test using Espresso (because now the list is private and shouldn't be exposed).
My question is: how to create a reusable RecyclerView using "Composition over Inheritance"?

How to play android sounds NOT in activity class

How can I play sound from a class that DOES NOT extend activity? I've been searching for a while and in every tutorial or answers from stackoverflow I've seen that sounds are always implemented in an activity class.
But in this case I have a class thas has the logic of my game, in which I have a gameUpdate() function; and in that function I want to make a specific sound play if something happens (for example a collision). How can I possibly access the activity that is currently running, from this class? Is there any way to do that?
Thanks.
If you need to get the current Activity instance or context you need to pass it to your other classes so that you can use it. For example:
class ABC extends Activity
{
public void onCreate(Bundle b)
{
XYZ xyz=new XYZ(this); // Sending the current Activity instance
}
}
class XYZ
{
ABC activity;
public XYZ(ABC activity)
{
this.activity = activity; //Now you can use it in this class
}
}
getActivity() or if is inside a fragment getFragment().getActivity()
Or alternativelly you can make add a Context to your class and get the activity reference from the constructor of the class.
Ex:
public class MyClass {
Context mContext();
public MyClass(Context context){
this.context = context;
}
}
and in your Activity class when you call MyClass:
MyClass myClass = new MyClass(this);
Inside your custom lass you can reference activity methods using its context.
So you actually just need a Context, not specifically an Activity (which is a Context). I would recommend that the class that should play sounds has a constructor which requires a Context. Keep a reference, not directly to the Context that you receive, but to the Application context using getApplicationContext() to get a Context that is safe to retain without the risk of memory leaks.
public class MySoundPlayingClass {
private final Context mContext;
public MySoundPlayingClass(Context ctx) {
// Since ctx could be an Activity, and this class
// could exist outside of the lifecycle of the Activity,
// grab the Application context to get a safe reference.
mContext = ctx.getApplicationContext();
}
}
Have a Util class and do something similar to below one. You can pass the context (it can be Activity instance) and the resource id to play it
Usage:
Util.play(context, R.raw.soundFile);
Sample Util class:
public class Util {
public static void play(final Context context, int resource) {
MediaPlayer mp = MediaPlayer.create(context, resource);
if (null != mp) {
mp.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
mp.release();
}
});
mp.start();
}
}
}

Categories