Using custom binding adapter to bind drawable resource - java

I'm trying to establish data binding between two properties of an object in my view model and my UI.
The object has the two properties name and iconName which I can access through Getter-/Setter-Methods in the view model.
The name property is a simple String field that gets bound to an EditText component.
The iconName property, however, is a Resource Name of an XML file located in the drawable directory which is supposed to get bound as the source of an ImageView component.
Changing the source of the ImageView component manually was as easy as calling:
int resId = getResources().getIdentifier(iconName, "drawable", getPackageName());
selectedIconView.setImageResource(resId);
in the Activity class.
But now I'm not sure how to extract the resource ID inside the Binding Adapter to update the ImageView since I seem to have no Context inside my Binding Adapter.
public class SubjectAdapter {
#BindingAdapter("app:subjectName")
public static void setSubjectName(EditText view, SubjectEntity subject) {
view.setText(subject.getName());
}
#BindingAdapter("app:srcCompat")
public static void setSubjectIcon(ImageView view, SubjectEntity subject) {
String iconName = subject.getIconName();
// TODO: Set Image Resource of view
}
}

If you have any View, getting a Context is as easy as calling getContext() on the view.
#BindingAdapter("app:srcCompat")
public static void setSubjectIcon(ImageView view, SubjectEntity subject) {
String iconName = subject.getIconName();
Context context = view.getContext();
String packageName = context.getPackageName();
int resId = context.getResources().getIdentifier(iconName, "drawable", packageName);
view.setImageResource(resId);
}

Have your adapter function receiving the value (resource ID):
class MyViewModel : ViewModel() {
companion object {
#BindingAdapter("app:srcCompat")
#JvmStatic
fun setImageViewResource(imageView: ImageView, resource: Int) {
imageView.setImageResource(resource)
}
}
}
This code is called from the generated class so it doesn't have to be in your viewmodel necessarily.

Related

How to access Room database from different fragments?

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())
}
...
}
}

Design pattern to build one or more side-by-side views

I am attempting to build a dynamic control in Android that uses the builder pattern to construct a view that looks like:
[** Text][** Text]
Essentially, I am looking to create a view with an image and some text and group multiple such views together.
public static class DualBuilder {
private final Context mContext;
protected Drawable mFirstButtonIcon;
protected Drawable mSecondButtonIcon;
protected String mFirstButtonText;
protected String mSecondButtonText;
private DualBuilder(#NonNull final Context context) {
mContext = context;
}
public DualBuilder firstButtonIcon(#Nullable final Drawable firstButtonIcon) {
super.buttonIcon(firstButtonIcon);
return this;
}
public DualBuilder firstButtonText(#NonNull final String firstButtonText) {
super.buttonPrimaryText(firstButtonText);
return this;
}
public DualBuilder secondButtonIcon(#Nullable final Drawable secondButtonIcon) {
mSecondButtonIcon = secondButtonIcon;
return this;
}
public DualBuilder secondButtonText(#NonNull final String secondButtonText) {
mSecondButtonText = secondButtonText;
return this;
}
public MultiViewControl build() {
return new MultiViewControl(this);
}
}
The above would be a builder if i were to have two of these views. The constructor would take the builder variables and use them to construct a layout for each first/second view. The details of how that happens are not the issue, I just want to learn how to build something like this in a flexible way so it can accept 1...n number of views. What kind of a pattern can i use to achieve this? Any code sample would be very much appreciated.
Google has released a new library called Fluxbox layout which will serve your purpose.
check out this https://github.com/google/flexbox-layout
It will add your view dynamically side by side as you expect.
My two pence here...I just came up with this on the fly based on my experience.
So as I see it, your basic view content is an image and a string. If we consider this as an object structure called ViewContent then we can imagine that the Builder takes a composite of ViewContent objects and therefore we can pass 1..n such objects as a composite as input to the Builder object which will then proceed to act upon all these objects and create a layout for you...Hope this helps.

Android: Resources.getSystem().getIdentifier returns java.lang.IllegalArgumentException

I'm tring to get an id of the picture by name
someId = "hd_2"
Resources.getSystem().getIdentifier(someId, "drawable", getPackageName());
public static final class drawable {
...
public static final int hd_2=0x7f020088;
...
}
Why it might return java.lang.IllegalArgumentException : Invalid method?
update:
I moved Resources.getSystem().getIdentifier from onCreate to onStart, now it returns 0.
From the documentation on Resources.getSystem():
Return a global shared Resources object that provides access to only system resources (no application resources), and is not configured for the current screen (can not use dimension units, does not change based on orientation, etc).
You are looking in the wrong Resources object. You need to get the instance from your current Context. Something like:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Resources res = getResources();
int resId = res.getIdentifier(someId, "drawable", getPackageName());
}
Maybe this can help you: https://stackoverflow.com/a/15488321/3633665
int resId=YourActivity.this.getResources().getIdentifier("res_name", "drawable", YourActivity.this.getPackageName());

Get context directly in PagerAdapter?

I need to use PackageManager pm = context.getPackageManager(); inside a PagerAdapter but I don't know how to get a context.
public class MyPagerAdapter extends PagerAdapter {
...
}
How can context inside PagerAdapter?
The answer of #DeeV means you must provide a context to your PageAdapter by yourself, either via the constructor or via a setter. Then store it inside the PageAdapter as a field and retrieve it whenever you need it.
private ViewGroup _container;
public override Java.Lang.Object InstantiateItem(ViewGroup container, int position)
{
_container = container;
}
private Context GetContext()
{
return (Activity)_container.Context;
}
It's not recommended provide Context, this may cause memory leaks,
if you need the context to access resources, you can provide the resources through the constructor.
MyPagerAdapter(val resources: Resources){
}
....
val adapter = MyPagerAdapter(context.resources)

Use Custom View In XML Layout

I have a custom view:
public class Loading extends View {
private long movieStart;
private Movie movie;
public Loading(Context context, InputStream inputStream) {
super(context);
movie = Movie.decodeStream(inputStream);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
super.onDraw(canvas);
final long now = SystemClock.uptimeMillis();
if(movieStart == 0)
movieStart = now;
final int relTime = (int)((now - movieStart) % movie.duration());
movie.setTime(relTime);
movie.draw(canvas, 0, 0);
this.invalidate();
}
}
How can I use this view in XML layout? How can I pass the parameters (Context, InputStream) in XML layout?
How can I use this view in XML layout?
..
<pacakge_of_class.Loading
android:id="#+id/y_view1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
http://developer.android.com/guide/topics/ui/custom-components.html
There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file.
How can I pass the parameters
https://stackoverflow.com/a/4495745/804447
Error referencing an inner class View in layout/main.xml
<view class="Your_package.MainClass$Loading" />
The short answer is you can't directly do that.
The long answer is that you can indirectly do that.
Add the view to the XML by its fully qualified name (as others have mentioned), then:
What you need to do is implement the normal constructors from View. Define a custom attribute that declares the resource to use to create the InputStream in your constructor. The view system will give you the context automatically, you'd then need to open the InputStream based on the provided attribute value.
You can use a custom View in an XML-Layout like this:
<com.your.package.Loading
android:id="#+id/y_view1"
... />
But you cannot use your own constructor, you have to use the constructors as shown in this answer.
So you have to access your Loading View by code an set the InputStream manually:
Loading yourView = (Loading) findViewById(R.id.yourLoadingView);
yourView.setInputStream();
where you have this setter method in your Loading class:
public void setInputStream(InputStream inputStream){
movie = Movie.decodeStream(inputStream);
}

Categories