Android Custom View's constructor is executed twice - java

I have faced an issue after creating custom view class in android. The issue is simply, when I create an object of the custom view, its constructor is executed multiple times.
Code of the main Activity Class
public class MainActivity2 extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
CustomView customView = new CustomView(this, null);
}
}
Code of the customView
package com.example.myapplication;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
public class CustomView extends View {
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
System.out.println("Custom View is executed");
}
}
Code of main Activity's xml layout file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="4"
tools:context=".MainActivity2">
<com.example.myapplication.CustomView
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.example.myapplication.CustomView>
</androidx.constraintlayout.widget.ConstraintLayout>
The expected result is: the constructor of the view is executed one time
Actual Result: the constructor of the view is executed two times!

You can put a breakpoint in the constructor to examine the call stack and to learn where it is called exactly.
The first invocation comes from setContentView when the layout containing your custom view is inflated.
The second invocation is your explicit constructor call new CustomView(...) in your activity code.

Related

Adding layout file to class (Java Android Studio)

When adding an (empty activity), the class generates with the appropriate layout file but in the setContentview(R.layout.activity_home) it gives an error.
This is my (empty) class:
package com.test.learnlogin;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class HomeActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
}
}
And this is the layout file to refer to:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
I am assuming that the name activity_home inside setContentView is of red color.
if yes and you are sure that activity_home file exists in Res>Layout then please Restart Your Android Studio, that All
You'll have to import R to remove that error.
import com.test.R // assuming your_package_name=com.test
at the top.

Cant call method of custom ConstraintLayout class?

I want to make a custom ConstraintLayout so I can trigger some functions from the activity where its initialized.
I have the following code:
First initialized my custom view in layout:
<package.com.app.Main.LavadasView
android:id="#+id/main_autolavados_lavadas_lavadas_view"
android:layout_width="0dp"
android:layout_height="25dp"
...
/>
This is my custom class LavadasView, constraint layout initialized from another XML file:
Java class
public class LavadasView extends ConstraintLayout {
public LavadasView(Context context,AttributeSet attrs) {
super(context);
//Inflate view from XML layout file
LayoutInflater inflater =(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.lavadas_view, this);
}
public void resetView(){
//Some ui updates
}
}
LavadasView xml file, just a normal constraint layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.constraint.ConstraintLayout>
And in my Activity I get the instance with findViewByIdmehtod, then I want to call the resetView function and that gives me a null pointer exception related to LavadasView:
LavadasView lavadasView = (LavadasView) findViewById(R.id.main_autolavados_lavadas_lavadas_view);
//Call this method later on
lavadasView.resetView();
SO what am I doing wrong? I've looked up and thats the correct way to get layout instance isnt?
Thanks.
The problem is with the constructor of your LavadasView. When your Activity is inflated it will call the LavadasView(Context context, AttributeSet attrs) constructor on you custom layout. In this constructor you need to call the super(context, attrs) for the ConstraintLayout to be properly inflated, but you're only calling super(context) which is why you get the NullPointerException.

Change orientation crash application cause android.support.v4.app.ListFragment

I don't know why application crash on orientation change while using ListFragment ( Support Version ) when i replace android.support.v4.app.ListFragment by android.app.ListFragment. Every Thing works fine. i don't want to use this method "android:configChanges="orientation".
as a solution of my problem.
When i rotate my device very first time after launching application ( from portrait to land ) Every
thing is fine
but when again ( land to portrait ) crash crash crash
i think problem is with support lib not my code.
MainFragment class code:
package com.workout;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
public class MainFragment extends ListFragment {
public MainFragment() {}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<>(getActivity(),
android.R.layout.simple_list_item_1,WorkoutDetail.workoutDetails));
if ( getListView() != null )
getListView().setOnItemClickListener((MainActivity)getActivity());
}
}
MainActivity Code:
package com.workout;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Intent intent = new Intent(this,DetailActivity.class);
// intent.putExtra(Intent.EXTRA_TEXT,id);
// startActivity(intent);
}
}
MainActivity XML:
<?xml version="1.0" encoding="utf-8"?>
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.workout.MainFragment"/>
Logcat Detail:
12-14 22:17:58.383 7159-7159/com.workout E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.workout, PID: 7159
java.lang.IllegalStateException: Fragment has not been attached yet.
at android.support.v4.app.Fragment.instantiateChildFragmentManager(Fragment.java:2308)
at android.support.v4.app.Fragment.getChildFragmentManager(Fragment.java:773)
at android.support.v4.app.FragmentActivity.markState(FragmentActivity.java:967)
at android.support.v4.app.FragmentActivity.onSaveInstanceState(FragmentActivity.java:527)
at android.support.v7.app.AppCompatActivity.onSaveInstanceState(AppCompatActivity.java:509)
at android.app.Activity.performSaveInstanceState(Activity.java:1419)
at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1301)
at android.app.ActivityThread.callCallActivityOnSaveInstanceState(ActivityThread.java:4637)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4588)
at android.app.ActivityThread.-wrap19(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1514)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:241)
at android.app.ActivityThread.main(ActivityThread.java:6274)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
i already visit many stackoverflow links but failed to find a solution of my problem.
try remove the listener implementation and onItemClick from activity and use onListItemClick that exist with the listfragment, you do not need to implement listener on listfragment just use the method as below, and hope this may solve your problem.
public class MainFragment extends ListFragment {
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
// put your code here
}
}
beside the above do the following (I have tested this and it is working)
build new XLM file as follows (copy and paste)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:id="#+id/FragmentSlot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
then dynamically add your fragment to the activity as below:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Begin the transaction
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
// Replace the contents of the container with the new fragment
ft.replace(R.id.FragmentSlot, new MainFragment());
// or ft.add(R.id.your_placeholder, new FooFragment());
// Complete the changes added above
ft.commit();
}
}
this will indeed solve your problem.
yes you are right it is something related to the system when inflating fragment from XLM.
to avoid this in future you need to learn how to use fragment manager and transactions to add, replace, and remove fragments.
check this one, contains three lessons: https://developer.android.com/training/basics/fragments/index.html

How do I link a View class to the XML screen?

I am still learning Java and how to use Eclipse but I have a XML main activity file (the graphical layout) and a Java file named Mainscreen that extends Activity.
Inside the Mainscreen file I have a Class named myView which extends View and I can get it to display on the device screen by calling setContentView(new MyView(this)); from the Mainscreen onCreate method. How do I make it so I can have the MyView be a view within the XML file?
I can also change it to setContentView(R.layout.activity_main_screen);and then it sets it to what the XML file is but how do I have it so both are displayed.
I want it to display the screen as if it was following the setContentView(R.layout.activity_main_screen); layout but then also have the MyView displayed as well inside a seperate View on the screen. I have tried setting it as setContentView(R.id.view1); but if I am being completely honest I am not 100% sure what I am doing as I am still learning.
Can someone please point me in the right direction or help me out? I have been Googling trying to figure this out and I'm a little lost.
Thanks
EDIT: added code below
XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainScreen" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:contentDescription="#string/hello_world"
android:src="#drawable/title_plate" />
<View
android:id="#+id/view1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/imageView1"
android:layout_centerHorizontal="true" />
</RelativeLayout>
The start of the MyView class
public class MyView extends View {
public MyView(Context context) {
super(context);
//setContentView(R.id.view1);
setContentView(R.layout.activity_main_screen);
The onCreate for the main file itself
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main_screen);
setContentView(new MyView(this));
//View circleView = (View)findViewById(R.id.view1);
//circleView = (new MyView(this));
//circleView = findViewById(R.id.view1);
}
I won't post all the other stuff the file contains as it would seem pretty pointless as it is just code to do what I want it to do.
Take this Class as your view class
package com.example.utils.views;
import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.widget.Button;
public class FontButton extends Button {
public static Typeface FONT_NAME;
public FontButton(Context context) {
super(context);
if (FONT_NAME == null)
FONT_NAME = Typeface.createFromAsset(context.getAssets(),
"fonts/Signika-Regular.ttf");
this.setTypeface(FONT_NAME);
}
public FontButton(Context context, AttributeSet attrs) {
super(context, attrs);
if (FONT_NAME == null)
FONT_NAME = Typeface.createFromAsset(context.getAssets(),
"fonts/Signika-Regular.ttf");
this.setTypeface(FONT_NAME);
}
public FontButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (FONT_NAME == null)
FONT_NAME = Typeface.createFromAsset(context.getAssets(),
"fonts/Signika-Regular.ttf");
this.setTypeface(FONT_NAME);
}
}
**Now in the Xml file**
<com.example.utils.views.FontButton
android:id="#+id/register_user_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="17sp"
android:background="#drawable/enter_pin"
android:onClick="#string/countryCodeClick"
android:text="#string/default_country_code" />
The XML view is a description of a class hierarchy. Each object in the XML represents a java class that inherits from View. The instances that contain other class inherit from a subclass, ViewGroup.
When you make a call like this: myActivity.setContentView(R.layout.activity_main_screen);
What you are in effect doing is instantiating all of the objects defined in the view and associating them with your Activity. You can use your custom View subclasses in the XML by supplying the full package name (e.g. com.myapp.MyViewSubclass) in the XML definition. Here is a concrete example:
<com.myapp.MyViewSubclass
android:id="#+id/myView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Now, within the java code, if you wanted to access this view, after calling myActivity.setContentView you would do something like this:
MyViewSubclass instance = (MyViewSubclass)myActivity.findViewById(R.id.myView1);
So R.java is a file that is created automatically by parsing all your XML files. R.id.myView1 is an integer that resolves to the item inside your XML file, and it will reference the object that you loaded once you load it using setContentView.

runtime InflateException when trying to use a Custom View in xml layout

I've been reading a lot of similar questions to try and find the solution for this but with no luck.
MainActivity.java
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.game);
}
game.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<View
android:id="#+id/adView"
android:background="#drawable/ad"
android:layout_width="320dp"
android:layout_height="50dp"
/>
<my.package.MainGamePanel
android:id="#+id/gameView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/> </RelativeLayout>
MainGamePanel.java
public class MainGamePanel extends SurfaceView implements SurfaceHolder.Callback {
private MainThread thread;
public MainGamePanel(Context context, AttributeSet a) {
super(context, a);
View.inflate(context, R.layout.game, null);
onFinishInflate();
thread = new MainThread(getHolder(), this);
setFocusable(true);
thread.setRunning(true);
etc. etc.
And then outside the MainGamePanel constructor is the function:
#Override
protected void onFinishInflate() {
getHolder().addCallback(this);
}
There is also a MainThread.java file but I don't think that is the problem.
This is the runtime exception:
java.lang.RuntimeException: Unable to start activity ComponentInfo{my.package/my.package.MainActivity}: android.view.InflateException: Binary XML file line #13: Error inflating class my.package.MainGamePanel
If I change the setContentView in MainActivity to setContentView(new MainGamePanel(this)) and remove the AttributeSet parameter from the constructor, and delete the View.Inflate(context, R.layout.game, null); , then it works, but I want to figure out how to use the custom view in the xml file.
it looks like you are circularly inflating layouts. you setContentView on R.layout.game, which contains a MainGamePanel, which inflates R.layout.game, which contains a MainGamePanel, etc.
you should take the View.inflate line out of the onCreate method. It's not doing anything anyway, as far as I can see. you also shouldn't explicitly call onFinishInflate. That will be called automatically when the inflation of the MainGamePanel instance is actually finished.

Categories

Resources