getLayoutInflater vs LayoutInflater.from - android

Studying some (known to be good) code I can see the logic as follows:
if (getContext() instanceof Activity) {
inflater=((Activity)getContext()).getLayoutInflater();
}
else {
inflater=LayoutInflater.from(getContext());
}
I wonder, why this if/else, how it is better to, just, using LayoutInflater.from in all cases?

It doesn't really matter much.
Activity delegates getLayouInflater() to Window. The usual policy implementation of Window PhoneWindow in turn initializes its inflater with LayoutInflater.from(context) where context is the activity.
So the inflater object is really the same, using the same Context in case of an activity.
LayoutInflater.from() is really a wrapper for Context.getSystemService(). Now, system services are looked up by name from a map and then retrieved from a cache. This lookup has some overhead when compared to accessing an already initialized member variable in Activity.
So it smells like a micro optimization that does not likely affect much runtime performance when compared to actual view hierarchy inflation.
This optimization can actually have negative impact on developer productivity as people need to stop and spend some thinking why the code is there.

By the same code it seams that LayoutInflater.from is used in the contexts that are not an activity. I would assume that using the Activity's inflater reuses an already created inflater versus the other choice which would create a layoutinflater from a context.
The only change I would make is saving the context in a variable to prevent calling the same function and retrieving the same value from the object repeatedly:
Context ctx = getContext();
if(ctx instanceof Activity) {
inflater = ((Activity)ctx).getLayoutInflater();
}
else {
inflater = LayoutInflater.from(ctx);
}
Android has a lot of optimizations in place to reuse items when available, like the views for ListViews which can be reused.

from the android documentation it is suggested to use getLayoutInflater( ) instead.The documentation says the following about the LayoutInflator.from.. :
Instantiates a layout XML file into its corresponding View objects. It
is never used directly. Instead,
it suggests to use :
use Activity.getLayoutInflater() or
Context#getSystemService to retrieve a standard LayoutInflater
instance that is already hooked up to the current context and
correctly configured for the device you are running on.
in other words for the sake of simpler code and performance you better use getLayoutInflater from the context that has already been initilized.

Related

Android - Best approach for passing context in adapter (Specifically `RecyclerViewAdapter`)

I have a RecyclerView with its Adapter in a Fragment. Currently I'm hunting for OOM causes and Context leaking might be one of the cause.
There are several approaches I did in getting the context in Adapter (Need the Context for SharedPreferences, Glide/Picasso, and replacing Fragments).
Passing the Context through the adapter constructor and then set it into global variable inside the adapter :
LobbyAdapter lobbyAdapter = new LobbyAdapter(this.getActivity);
Have a global Context in the Adapter and take the Context from onCreateViewHolder :
context = parent.getContext();
This causes problem when I new the Adapter using SwipeRefreshLayout. But this must be because of the flawed logic I did in the Fragment, still tracking this down.
Don't make a global Context variable, but, get every Context from the View from every ViewHolder related to the Context
Loading an image
The key here is using the holder to get the Context ((FriendProfileViewHolder) holder).coverPhoto.getContext()
Glide.with(((FriendProfileViewHolder) holder).coverPhoto.getContext())
.load(utilities.webAddress + profileDataModel.user_cover_image_path)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.skipMemoryCache(true)
.centerCrop()
.into(((FriendProfileViewHolder) holder).coverPhoto);
In this part, due to my lack of experience with Context, I'm not sure which view should we get the Context if one method is reused by different Views
.
Additional question : (This might need new question thread..)
In several Adapters, I do an AsyncTask to get response from server to change image. And I need Context in the Interface to do getPackageName() to get package of the app, and getResources() to access resources.
String pictureName = output.image_name_profile;
String packageName = context.getPackageName();
if(!pictureName.equals("default")){
resId = context.getResources().getIdentifier("drawable/" + pictureName, null, packageName);
image = context.getResources().getDrawable(resId);
}
Maybe I should create a global variable and method to mutate those values?
I've used the first approach that you described in your question, the one for passing Context through a constructor and then set it to global variable for later use. This approach works great for Fragments containing RecyclerViews. Used it for a couple of times and it has never caused a Context leak problem.
I've not used any of the other approaches so will not comment on them.

Passing values to custom view in android

Cheers,
I have an app that receives user input (2 numbers, width and height) and in theory depending on that input I have a custom view that should draw a grid (width and height).
Note:
These 2 values should be received before view attempts to draw itself.
These 2 values aren't constant and therefore I don't think XML approach can help.
I was told that adding another parameter to the View constructor is evil.
Do not confuse my 2 values with canvas.getWidth or etc.. these are values needed simply to draw something, nothing else.
My View is also a ViewGroup.
Main issue arises with Views declared in XML files.
I have temporarily solved this issue by making an SchemeContext class which contains those 2 static values and I simply set them in onCreate (before onCreateView) then use them in custom View onDraw when needed (SchemeContext.width). This is not really what people would call OOP I'm forcing global variables upon java and those are set on time because of the fragment lifecycle.
I've seen this answer How to pass variables to custom View before onDraw() is called?.
But it's more of a workaround than a solution (and probably not the fastest one). There has to be a sensible solution I don't think 3D games on android resort to these workarounds (SurfaceView with OpenGL is still a View right? :d).
If there is an obvious solution and this is an obvious double I'll remove the question.
I haven't tried this, but I think it would be possible to do this fairly cleanly by overriding the LayoutInflater.Factory. That way, you can intercept the creation of the views that need additional parameters passed to their constructors, and let the rest of them fall through to default inflation.
For example, in your activity, before you inflate the view hierarchy:
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
MyInflaterFactory factory = new MyInflaterFactory();
// Pass information needed for custom view inflation to factory.
factory.setCustomValue(42);
inflater.setFactory(factory);
For your implementation of the factory:
class MyInflaterFactory implements LayoutInflater.Factory {
public void setCustomValue(int val) {
mCustomVal = val;
}
#Override
public View onCreateView (String name, Context context, AttributeSet attrs) {
if (name.equals("com.package.ViewWithCustomCreation")) {
return new ViewWithCustomCreation(context, attrs, mCustomVal);
}
return null;
}
private int mCustomVal;
}
I was told that adding another parameter to the View constructor is evil.
Nonsense.
There are three (and in the newest APIs, four) different View constructors, each used in a different situation. (See this thread.) If you wanted to be able to declare your view in XML, for example, then you'd have to provide a constructor with exactly the right parameters, and have it call the corresponding superclass constructor. But there's nothing wrong with defining your own constructor (or even several of them) that call the superclass constructor intended for creating views programmatically.
The overriding principle is that every object must be valid when its constructor returns. So unless you can provide reasonable default values in your constructor, you have little choice but to accept the object's properties as constructor parameters.

How to find a View in a BridgeContext?

TL;DR: Is there anything in com.android.layoutlib.bridge.android.BridgeContext that can substitute for Activity#findViewById(...)? I've looked at the source, but I can't find anything.
When running on a real device, an attached view's #getContext() returns the Activity. The view can cast it and call #findViewById(...) to obtain a reference to some other view.
But when running in a WYSIWYG editor, #getContext() returns an instance of a different class. I'm getting com.android.layoutlib.bridge.android.BridgeContext. This class isn't part of the public API, so I'm planning to access it via reflection and degrade gracefully if the implementation changes.
If you're wondering why my view wants a reference to another view... I've created a view that appears to have a hole in it. It works by delegating its drawing to another view. If the view with the hole is placed on top of other views, then it appears to punch a hole through any views beneath it, all the way down to the view it's using for drawing. It works perfectly on a real device, but it would be nice to have it also work in the WYSIWYG editor.
It's bad to assume that View.getContext(), or any other platform method that returns Context, can be cast directly to more concrete classes, like Activity. There exist classes like ContextThemeWrapper which can easily destroy your assumption.
I would recommend restructuring what you are doing so that you have a parent layout that can act as an intermediary for the hole-y View and what's below it.
Or you could have a setter which would provide the View for you.
A last option is to call View.getParent() a bunch of times to get the root View and call findViewById() on that:
ViewParent parent;
while(getParent() != null) {
parent = getParent();
}
View root = (View) parent;
root.findViewById(R.id.my_view);
BTW, BridgeContext is used in the WYSIWYG in place of Activity because it only mocks the Android View/Layout/Rendering system, it doesn't emulate it completely. This can be seen in other ways like how it renders shadows or shape drawable rounded corners.
I awarded the bounty to dandc87 because his answer led me to the solution. However, the code snippet in his answer crashes with a ClassCastException because the root ViewParent is not a View. The mods keep rejecting my edits, so here's the complete and correct solution:
private View findPeerById(int resId) {
View root = this;
while(root.getParent() instanceof View) {
root = (View) root.getParent();
}
return root.findViewById(resId);
}

Does Android API have something like Flash's duplicate movieclip?

I used to do alot of Flash Actionscript and now I am getting into Android. Is there something in the Android API that is similar to duplicateMovieClip() in Actionscript? I'm sure there is probably a way to write such a method, but I am wondering if there are any existing shortcuts.
For example, say I have an ImageView, TextView, or other kind of View Object on screen and I want to have a button to click which will make a duplicate of some object on screen.
If you don't mind my asking, why do you need something like duplicateMovieClip()?
To answer the question, Android doesn't have a notion of the AS2 duplicateMovieClip(). Much like in AS3 (which also didn't have duplicateMovieClip()) you'll have to implement your own cloning method. Java does have an unimplemented '.clone()' method as part of every Java object, so if there's a particular View you would like to clone you might be able to implement your cloning there by
Overriding the clone method.
I think what you'd probably end up doing instead is doing something more akin to instantiating from the Library by making small view layouts in xml and inflating them using the Inflater tools.
View result = null;
// where pContext is a context object, either supplied by the application
// or just by the current Activity (if available)
LayoutInflater inflater = (LayoutInflater) pContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// where id is the layout id such as R.layout.myclonableview.
// where pRoot is the parent container for the new result.
// where pAttachToRoot is whether to immediately inflate the new view into the root.
result = inflater.inflate(id, pRoot, pAttachToRoot);
// Now "clone" your old view by copying relevant fields from the old one to the
// one stored in result

Get Android View Instance

This is a dumb question and I know the answer is sitting in front of me, I'm just having trouble searching for it in the right way.
I've got a custom view that has been set as the content view and inflated from xml. How can I access the instance to call methods on it from the activity class? I remember seeing something akin to getResourceById() a while back, but now I can't seem to find it and I'm not even sure if that's the best way to do it.
Sorry for the dumb question.
If you have used an inflater, you will be given an instance of a View class. You then use your instance like so
LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = li.inflate(R.layout.small_listview_row, null);
TextView tvItemText = (TextView)row.findViewById(R.id.tvItemText);

Categories

Resources