I'm fairly new to Java & Android, so I have little idea what I am doing.
My test program successfully creates view objects, so now I am trying to organize my code.
I want to make a separate class to manage my GUI, but it always fails.
Basic info about my gui class:
package horc.gui;
...
public class GUI{
private Context context;
From my main activity, I constructed that gui class with the app context.
gui=new GUI(getApplicationContext()); // gui is a var of type GUI, & this sets the context of the class
The problem is when I make/modify a view object that is in the class from outside, it throws an exception.
My main activity...
package horc.test;
...
GUI gui;
LinearLayout test=gui.newLinear("ff", "v"); // <-- this sets fill parent for width/height
// & vertical orientation of the vertical layout.
// Doesnt work for the reason stated above.
// I cannot manage any view objects from a separate class.
gui.lastText.setText("##########"); // <-- a variable in the class to hold the view object I am manipulating
setContentView(t);
...calls this class function:
public TextView newText(String text){
TextView test=new TextView(context);
lastLinear.addView(test);
return test;
}
I tested this similar body within the main activity & it worked fine. It only fails when I do anything from outside that gui class.
Is there a common issue that people run into when managing view objects in separate classes? I have absolutely no idea what I am doing at this point. Coming from C++, java seems like a nutty language. I cannot plan things the way I would in C++.
instead of
gui=new GUI(getApplicationContext());
try
gui=new GUI(MyActivity.this);
Please put your activity name instead of MyActivity
Related
I'm creating an app that has screens that present data to the user.
Each Screen has its own data and its own layout, so it has a method to return an int that represent the layout that is used to inflate it, then this View is passed to a function to find the specific views and populate it with data.
The lifecycle goes like this:
MainPresenter:
screen.getNextScreen ->
screen.getLayout ->
view = inflateScreen ->
screen.populateScreen(view) ->
(wait for time elappsed or click) -> repeat
Those Screens are also needed in SettingsActivity to enable\disable them.
So i've created a singleton ScreenProvider, it initializes once and then returns the list.
public class ScreenProvider {
private List<Screen> screens;
private static ScreenProvider instance = new ScreenProvider();
public static ScreenProvider getInstance(){
return instance;
}
private ScreenProvider() {
screens = new ArrayList<>();
screens.add(new Welcome());
screens.add(new CompoundScreen());
screens.add(new Times());
screens.add(new Messages());
screens.add(new Weekly());
}
public List<Screen> getScreenList() {
return Lists.newArrayList(screens);
}
}
Its seam that when running to long the app crashes or get closed for memory leaks, so i've added leakcanary, and this an example for its report:
MainActivity has leaked:
D: * static ScreenProvider.!(instance)!
D: * ↳ ScreenProvider.!(screens)!
D: * ↳ ArrayList.!(array)!
D: * ↳ array Object[].!([0])!
D: * ↳ CompoundScreen.!(disposable)!
D: * ↳ LambdaObserver.!(onNext)!
D: * ↳ -$$Lambda$Screen$67KdQ1jl3VSjSvoRred5JqLGY5Q.!(f$1)!
D: * ↳ AppCompatTextView.mContext
D: * ↳ MainActivity
This is just a single example, but almost every screen has such leak.
The LeakCanary report shows that TextView has this: D: | mAttachInfo = null so i assume it is not the problem.
Also every Screen has an onHide() to clear disposables, that is called when current Screen hides and in MainActivity.onStop().
How to fix this leak?
Should i not use a singleton for the screens?
If not, how do i access the screen list from other activities?
** Editing **
Adding some of Screen main methods that every screen overrides.
public abstract int getLayout();
public boolean shouldShow()
public void populateData(View view)
public void onHide()
public abstract int getScreenIndex();
public boolean shouldCacheView()
public int getDuration()
Okay. From what you are saying, and what you are showing, it seems you are keeping instances of some of the generated views into the Singleton. Don't. Every View requires a Context to be created, either by code, or by inflation (which is basically an XML-backed, reflection-based factory method), to access the app and system's resources, and keep a reference to said Context, for as long as they live. In your scenario, that means keeping references to the activity where you generated the view. Usually, regarding views and activities, this is what happens regarding the GC:
GC: Hey! Does anybody need this... MainActivity class?
View: I do! I do! I have a reference!
GC: Okay... and besides MainActivity, Does anybody else need this View class?
-Nobody answers-
GC: It does not matter my friend, you are being collected as well. Come with me.
And they both go.
In your case:
GC: Hey! Does anybody need this... MainActivity class?
View: I do! I do! I have a reference! and MainActivity references me as well.
GC: Okay... and besides MainActivity, Does anybody else need this View class?
ScreenProvider: I do.
GC: Okay, keep moving View, and take MainActivity with you. Let me know when you folks are done so I can collect you.
And thus the leak.
In order to pass a view from one activity to another, you would need to remove the reference (the mContext field) to the previous activity. Since there's no API for doing that, you would need to use reflection. And there arises another problem: Every UI piece is a subclass of View. Layouts, Widgets, etc, etc. So either you keep a reference to every piece of your XML file in order to remove the context via reflection, or you traverse the child list of the view, remove the context, and keep going until there are no more child views, at any level. After that, you would have to set the reference to the new activity in the same way. It sounds like a huge hack, because it is, and things are bound to break at some level. A Context after all represents the environment and state within your view exist.
A better solution for your situation is to remove the view references from the singleton, and use it only to keep representations of the state/configuration of a given view. Create a callback backed method (or similar) that inflates the view in background and performs the necessary configurations before returning said view. If you still want to keep a single repository of all the Screens the activity may have, add it to the activity class as a member, so it gets collected alongside the activity.
As a side note, your situation suggests you should use a single activity, and then just swap a "MainScreen" composed of "Screens", or just switch between screens depending on the situation. It would make more sense and would be less risky.
Finally, citing myself: Remember the first rule of the android fight club
Let's say I have a layout like this:
Creating the layout is not complicated, if the questions are fixed. But my requirement is to display the questions from database, like this:
As you can see, there are 4 sectionIds. That means we have 4 categories. I'm thinking to use LinearLayout for this. Then for each categories, we have different amount of questions. If the question type is R, use RatingBar. But if the type is D, then use TextArea. I also plan to use LinearLayout for each questions. Now the challenge is creating those layout dynamically, which I think is not that easy. What's the least complicated way to do this?
I have had to do this in several opportunities, so I wish I can give a tip or more:
If your problem is not complex, look for something that has already solved it:
This library create a layout based on Firebase data
This library is for creating forms
Most of the times, any library will work because programmatic views are tight to specific requirements, so you will have to go on your own.
Customized Solution
Creating programmatic views it has a skew learning curve but with time you will be able to solve it.
Defining your fields:
Create fields or partial package and put those classes inside. You want to create a class for each type of field so you can reuse easily and modify it is done in that class.
Also, define what is common for every field. You can do this with an interface:
interface FormField {
String result();
boolean isValid();
void setError();
}
In this case, this interface will allow you to handle the result of the field, know if it is valid and set the errors. Validations should be inner once are requested, and error should be settable do internal validations and from outside.
The result you are getting can change by specific methods in other classes but having a common String is most of the time useful even for showing a summary to the user.
There is another benefit, you can create a list of your fields by your interface, every fields implement it:
List<FormFields> fields = new ArrayList();
//fields.add(ANY CLASS THAT IMPLEMENTS THE INTERFACE);
Creating a Field
Start by creating a class that extends a suitable view for your field:
public class InputText extends EditText implements FormFiel {
//You can simply add customizations in the constructor
}
public class InputText extends LinearLayout implements FormField {
//Maybe you need an input with a label, hence a TextView and an EditText will go inside of this
}
The rule of thumb for this task about constructor is one param for java two params for xml. So wherever you need to put the view, you use either one of those.
Handling Field appearance
If you need something simple maybe just set everything in the constructor.
public class InputText extends EditText implements FormField {
//You can simply add customizations in the constructor
public InputText(Context context) {
super(context)
setLayoutParams(new LayoutParams(LayoutParams.WrapContent...)
}
}
if you need something more complex use the layout inflater.
public class InputText extends LinearLayout implements FormField {
//You can simply add customizations in the constructor
public InputText(Context context) {
super(context)
LayoutInflater layoutInflater = LayoutInflater.from(getContext())
//You have to create the layout, a neat trick is create it inside a linear layout eand then use refactor/extract
//The last boolean in the method attach it to the view
layoutInflater.inflate(R.layout.field_input_text, this, true;)
//Maybe you want to find something, this could be a field variable
TexteView tv = this.findViewbyId(R.id.inside_the_inflated_layout)
}
}
Define a model
You need to define a model for all the field types
public class FireField {
private String label, hint, type;
// Empty constructor and getters and setters
}
Create a Container class
The container class probably is a linear layout with orientation vertical, you want to create it anyway and added in the xml (remember 2 params constructor) so you can add inside that view a way to get all your fields, a special method for that.
It can be a list of fields, it can be the list of the data you need to send.
This way, fiedls take care of their own logic and form take care of the general logic.
Add the views
You have to fetch your data then do a loop and for each type of the data, add a new view, this is when the List<FormField> of views come in handy, following is pseudo code
List<FireField> data = new ArrayList();
//You can also have String, View map here, where the key is the type and the value View is your field
List<FormField> fields = new ArrayList();
for (DataSnapshot children: snapshot.getChildren()) {
FireField field = children.getValue(FireField.class);
data.add(field);
if (field.getType.equals("INPUT_TEXT)) {
//Here Im addinf the field in the constructor, then the view should take care of it
new InputText(context, field)
//Here I'm initializing the view, the view inside that method should set labels and other
InputText input = new InputText(this);
input.initialize(field);
fields.add(input);
//You can add it here or in other place re using some of the lists above
container.addView(input);
}
}
Finally use any of the list and the container method to get the data and send it to Firebase
I have an android app that I have decided to rewrite, one of the reasons for the rewrite is because I could have 10+ TextViews with text set based on a variable in a class e.g.:
MyClass myClass = new MyClass();
myClass.myNumber = 5; // inside MyClass - public int myNumber;
LinearLayout mainLayout = (LinearLayout) view.findViewById(R.id.mainLayout);
TextView myTextView = new TextView(getActivity()); //In a fragment
myTextView.setText(String.format("myNumber currently has a value of %d", myClass.myNumber));
mainLayout.addView(myTextView);
return view;
Up until now I have been using .setOnClickListener on the buttons/views that change myNumber, to set the text of the view again when the value of myNumber changes, which then calls .invalidate() to redraw the TextView, this has worked fine, but I use this method very heavily and code is getting quite repetitive and changing one integer can affect quite a lot of views (all of which use it differently - such as different wording, or a calculation (e.g. myNumber * 2)). I guess it's because it's made an immutable string in the TextView.
I have tried to create a custom TextView that implements Observer (making MyClass extend Observable) and in the update method I can get it to invalidate itself for the refresh, but it still has the same text. I have also tried creating single element arrays, in an attempt to pass the reference not the value in the hope that when it is changed and then the view is invalidated it will pick up the new value but the text still ends up remaining the same.
How can I get a TextView that will auto update when the value of myNumber has changed? Some sort of binding?
Bindroid works perfectly for this, just a note for users, using fragments the sample application is using this from an Activity so the bind method using Activity is called, so in the fragment I was using getActivity() which caused it to not work properly, digging around in the library I found a bind method that takes a View and passed in my view which gets inflated in the fragment and it works great!!! This is super easy to integrate btw it was just me not getting it!
Background:
I'm looking for a way to extend the GridView I need implement a col- and row-span like in HTML. I have hunderds of elements so I cannot use e.g. a TableLayout. However that would be too localized. So far I reduce the question to how to extend the GridView?
What I have tried and where I have failed:
My first approach was to look which methods I need to replace and I found some private methods which cannot be overriden. So I tried to copy the hole source code from the GridView to a new class called CustomGrid. That was a terrible failure since I cannot access the com.android.internal.R class.
Then I dropped that idea and looked if I can normal extend the class and replace all the private methods with custom copies. I took a pen and build a huge tree where all methods are used.
After I found all references I tried to extend the class normal and I added the first method:
private View fillDown(int pos, int nextTop) {
View selectedView = null;
int end = (mBottom - mTop);
if((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
// ...
Here is my next problem that member variables mBottom and mTop are unknown. I digged a little through the sources and found them finally in the View class, but unfortunately they are hidden.
/**
* The distance in pixels from the top edge of this view's parent
* to the bottom edge of this view.
* {#hide}
*/
#ViewDebug.ExportedProperty(category = "layout")
protected int mBottom;
Question:
How can I extend the GridView without hitting that lamentations and without the usage of massive reflection? I mean it seems to be impossible to write that as a pure custom control.
How can I extend the GridView without hiting that limentations and without the usage of massive reflection?
Most likely, you don't. You copy the code into your project and modify to suit, including all requisite superclasses (up to ViewGroup). I will be stunned if you can achieve your aims by a simple subclass of GridView. You may even have to completely write your desired widget from scratch.
That was a terrible failior due I cannot access the com.android.internal.R class.
You will also need to copy over relevant resources, then fix up R references to point to your own resources.
but unforcantly they are hidden.
You find other ways of getting this data. mBottom, for example, can be changed to getBottom().
In my Android app, I have two custom view classes - PortraitClass and LandscapeClass. They both do the same thing. On running the app, the view class fetches some pictures from an SDCard and then maniputlates (skew etc) and displays them. The only difference between the two classes is that the layout of pictures is slightly different on the screen.
I have two display.xml files (one under layout folder and the other under layout-land). The one under the layout folder adds Portrait and the other adds the Landscape class.
On orientation change, I would like to send information (picture numbers and few bitmaps) from one class to another so I won't have to fetch all the bitmaps again and also so that I display the ones that were being displayed.
I find the parcelable thing kind of confusing. I tried following this_example, but I noticed that in onRestoreInstance, the Parcelable has null for the "mSuperState" and I get a classCastException # "SavedState ss = (SavedState)state". The integer (picture number) that I am trying to pass is there. I am not sure what I am doing wrong.
You could use a global singleton in the Application class. For example an "AssetManager" is available here:
public class YourApplication extends Application {
public AssetManager assetManager = new AssetManager();
#Override
public void onCreate()
{
super.onCreate();
}
}
You can then call it from another activity:
YourApplication application = ((YourApplication ) this.getApplication());
application.assetManager.someFunction();
Not sure if this is what you are looking for but my stuff always works in orientation change with onCreate(Bundle savedInstanceState). Mine is all text in edit text boxes though so I am not sure how it would work for you.
Also check this out about midway down under "Persisting State Information During Configuration Change"
http://www.devx.com/wireless/Article/40792/1954