Passing resource id from Activity to initialize custom ViewGroup - android

This is a followup of my previous question where #pskink adviced me to implement custom ViewGroup that will paint what I need. I succeeded with a prototype using hard coded values. I want to move it to further level where I can pass initialization parameters from an Activity. I need to pass a resource id and open an image to be used in onPaint method.
This is the activity. I can get the ViewGroup instance there but it is already instantiated so it makes no sense to pass the resource id in its constructor. I tried to use a setter but I need a Context to initialize Drawable from the resource.
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_puzzle);
hiddenPicture = (TileGroupLayout) findViewById(R.id.hidddenPictureArea);
hiddenPicture.setPictureResource(R.drawable.pic_cute_girl);
ViewGroup's setter does not have the context like the constructor.
public void setPictureResource(int resourceId) {
int pictureResource = resourceId;
mCustomImage = context.getResources().getDrawable(R.drawable.pic_cute_girl);
pictureRect = mCustomImage.getBounds();
}
How to get from this issue? I need to pass the initialization parameter before ViewGroup is painted. Activity has many onXY() methods to override but there is no similar ViewGroup methods. What is its lifecycle?

inside your ViewGroup class, just call getContext() from anywhere inside of the class.
Edit
on top of getting the context from the ViewGroup class you actually don't need to call getContext().getResources() instead you should call getResources() directly.

Related

How to implement Fragment and AppCompatActivity in a single activity in android

I have implemented swipe in tablayout of my activity call Performance_Medicine
public class Performance_Medicine extends Fragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
//Returning the layout file after inflating
//Change R.layout.tab1 in you classes
return inflater.inflate(R.layout.performance_medicine, container, false);
}
}
Now, I am trying to implement cardview in same activity. But getting error
like below image
You are getting the error because you are trying to pass an instance of Performance_Medicine which extends Fragment. You need to pass in a context which you can do via this.getActivity() or this.getContext(). If you can pass the application context via a singleton or this.getActivity().getApplicationContext() then you may be better off memory wise.
Remember that a Fragment has its own lifecycle, but it runs in the context of its host Activity, that means you can not use 'this' for getting the Context, instead you need to use getActivity(). Also, as sam_c says, in your onCreate() method, the last line of code must be the 'return...' since this method has the return type 'View', and if you call the return statement, the method won't execute anything after this. Hope this helps to clarify.

How do I communicate between fragments as if they were all on one activity?

I am using the android studio fragments sample. My goal is to set the value of EditText et1 in fragment1 by using code in fragment2. Also I want to update the ListView lv, that is in fragment1 by using code in fragment2.
The example does some back and forth sending of ARG_SECTION_NUMBER, but that is just text and won't work for a listview. What does not work is this
private View rvza, rvl, rvea;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rvza = inflater.inflate(R.layout.fragment_zeit_und_aktion, container, false);
rvl = inflater.inflate(R.layout.fragment_liste, container, false);
rvea = inflater.inflate(R.layout.fragment_eintragen_und_aendern, container, false);
....
}
...
Button btnNeu = (Button) rvea.findViewById(R.id.btnNeu); //this line is somewhere...
What also doesn't work is this
new MainActivity().myfunction((ListView)rvl.findViewById(R.id.listView));
it gives an Attempt to invoke interface method 'int
android.widget.ListAdapter.getCount()' on a null object reference at
public void myfunction(ListView lv)
{
int count = lv.getAdapter().getCount();
So how do I communicate between fragments having one parent activity? Is there a helper class?
If you want to access any method or variable, that has an use by more than one child, make parent activity singleton and access the desired method or variable in child fragment. Also if you want to access anything within fragment from anther, don't forgot to make and init an object of that fragment in parent activity and use that object further to add or replace fragment in fragment manager. By this you have access to every child through the parent activity
How to make singleton? See below example.
public class SingletonExample {
// Static member holds only one instance of the
// SingletonExample class
private static SingletonExample singletonInstance;
// SingletonExample prevents any other class from instantiating
private SingletonExample() {
}
// Providing Global point of access
public static SingletonExample getSingletonInstance() {
if (null == singletonInstance) {
singletonInstance = new SingletonExample();
}
return singletonInstance;
}
public void printSingleton(){
System.out.println("Inside print Singleton");
}
}
There is an official guide on Android developers on how to communicate between fragments:
https://developer.android.com/training/basics/fragments/communicating.html
Basically, your fragment A will talk to the activity via an interface and the activity will look up fragment B and call some method on the fragment.
If you want to decouple things more, however, you could use something like otto event bus:
http://square.github.io/otto/
It can be used to communicate between different parts of your application without having to deal with the underlying details.

Is it possible to disable access to fragment's layout(views) from parent activity?

I'm working on a library which will provide fragment with some input fields in it. These input fields will contain user's private information that app which uses my library should not have access to. Therefore edittexts or we can say fragment's layout cannot be accessed from activity(findViewById,getChildAt..) where this fragment is attached to.
Usage of dialog, or another activity is not acceptable, this fragment should be included directly in activity's layout.
Is this even possible in Android ?
I was thinking of creating views dynamically, and overriding methods such as getChildAt to prevent access to child views, but before I start "playing" with this problem, I'd rather ask here for some opinions.
Android does not provide a model for such a usage.
Overriding methods will certainly make it harder to access these views, but not impossible. Your custom view class has to store its children somewhere. Even if that is a private field, reflection can access it.
An activity has full control over his content, and I don't think you can prevent that.
First of all what you want is not a good approach, and what i am suggesting is just an idea, its not tested and recommended but can do your work
Create class BaseFragment and extend you each class with Base Fragment
must override its getView()
In these approached you have to remove root view as a class member getView returns the same
public class BaseFragment extends Fragment {
#Nullable
#Override
public View getView() {
super.getView();
}
}
Now you can do it in two ways
Create boolean in BaseFragment with private access boolean canAccess = true; with no getter and setter and change definition of your getView() to
public BaseFragment() {
canAccess = false;
}
#Nullable
#Override
public View getView() {
if(canAccess)
return super.getView();
return
null;
}
You must call super() for your every child constructors, now if you access getView inside the class canAccess is true so you will get actual view otherwise you will get null.
As per documentation
Get the root view for the fragment's layout (the one returned by {#link #onCreateView}),
if provided #return The fragment's root view, or null if it has no layout.
Second option is much simplest
#Nullable
#Override
public View getView() {
try {
throw new Exception("Who called me?");
} catch (Exception e) {
String className = e.getStackTrace()[1].getClass().getCanonicalName();
if (className.equals(YourParentActivity.class.getCanonicalName()))
return null;
else
return super.getView();
}
}
You can disable contents inside your fragment using following method:
public void enableDisableViewGroup(ViewGroup viewGroup, boolean enabled) {
int childCount = viewGroup.getChildCount();
for(int i = 0; i < childCount; i++) {
View view = viewGroup.getChildAt(i);
view.setEnabled(enabled);
if (view instanceof ViewGroup) {
enableDisableViewGroup((ViewGroup) view, enabled);
}
}
}
You can simply call above method as follows:
enableDisableViewGroup((ViewGroup) rootView, true); // disable
enableDisableViewGroup((ViewGroup) rootView, false); // enable
This method will work for both fragments and adapters to disable/enable their contents.
I did not understand it correctly I guess, but I think that anything created by private access mode cannot be accessed from outside.
Did you ever consider using webview for your particular problem !!!
make a fragment and in it show your desired webview and let user input anything he likes.
by this the OTHER APP wont have access to the EditTexts.
You can override TextView's getText() and return null for private views. If someone will get this text view - he will not be able to get it's content.

Memory leak using the LayoutInflater to inflate an XML

I am facing a very strange issue. I have an Activity, with the ActionBar containing a custom view.
If I load the XML of this custom view like this:
LayoutInflater inflater = LayoutInflater.from(this);
ViewGroup vg = (ViewGroup) inflater.inflate(R.layout.action_search_form,null);
It leaks: the activity is not GC'd. The following fix works (why?):
LayoutInflater inflater = LayoutInflater.from(getApplicationContext());
However if I set a OnClickListener on a child view it leaks again:
ImageButton clear = (ImageButton) vg.findViewById(...);
clear.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Even if empty
}
});
}
All this happen in a private method of the activity and all views are local variables.
I found a fix but do not understand why it works: The view was simple so I coded it in Java instead of inflating the XML. I suspected it is related to the fact that no context is passed to the LayoutInflater and nothing happens out of the Activity itself, but if someone could help me understand what happened I would greatly appreciate it.
All View classes require a Context parameter. When you are inflating the XML, the context provided to the created View is the same context provided to the LayoutInflater. Your view is retaining a reference to your activity.
The reason why when you use getApplicationContext() and the Activity doesn't leak is because you are passing a context that is global to the entire lifecycle of the application, not just the activity.
** EDIT **
In regards to why the OnClickListener is also retaining your Activity, it is because of a side effect of using anonymous inner classes. By default, these inline implementations retain a reference to the parent / wrapping class instance. This is so you can call methods of the parent from the implementation of the inner.
For example:
myView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
MyActivity.this.doSomethingCool();
}
}
Whether or not you have anything in the method body, the inner class still maintains a reference to the parent class.

Android: How to get a view given an activity inside a normal class?

I have a normal class (not an activity). Inside that class, I have a reference to an activity.
Now I want to access a view (to add a child) contained in the layout xml of that activity.
I don't know the name of the layout file of that activity. I only know the ID of the view, which I want to access (for example: R.id.my_view).
How can I do that?
Regarding the NullPointerException (which you should add to the question), always make sure you've called setContentView() in your Activity before trying to access a View defined in XML. Example usage:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
}
...
}
Then, somewhere,
ViewGroup group = (ViewGroup) context.findViewById(R.id.group); // In your example, R.id.my_view
The reason you need to have called setContentView() is that before it's called, your View(Group) doesn't exist. Because findViewById() is unable to find something that doesn't exist, it returns null.
As simple as that!
View view = activity.findViewById(R.id.my_view);
In case of the Layout:
LinearLayout layout = (LinearLayout) activity.findViewById(R.id.my_layoutId);
And to add the Views:
layout.addView(view);
You could make your method accept an Activity parameter and then use it to find the view by id.
Ex:
public class MyClass{
public void doSomething(Activity context){
TextView text=(TextView)context.findViewById(R.id.my_textview);
}
}
Then in your activity:
obj.doSomething(YourActivity.this);

Categories

Resources