I'm still new to Android programming, so this question is rather basic. I see lots of code samples on the Internet where UI components, like a TextView, are being initialised and accessed in the onCreate() method of an Activity.
When I use Android Studio to create a new project - FirstApp - with default settings, a blank Activity called MainActivity bringing along with it activity_main and fragment_main, I can immediately compile this into an APK, deploy it on my device, and I get a screen with a header "FirstApp" and a TextView in the top left showing "Hello world!".
If I give the TextView an ID of textview1, and create a member variable, TextView myTextView;, then I can reference it in the onCreate() method of the Activity, like so (no compiler errors, of course):
#Override
protected void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
setContentView (R.layout.activity_main);
myTextView = (TextView) findViewById (R.id.textview1);
myTextView.setText ("Hello tablet!");
if (savedInstanceState == null)
{
getSupportFragmentManager().beginTransaction().add (R.id.container,new PlaceholderFragment()).commit();
}
}
However, if I compile and run the APK it results in an "Unfortunately, FirstApp has stopped." message.
I have previously got around this issue by moving startup code that accesses UI components into the onStart() method of the Activity, like so:
#Override
protected void onStart()
{
super.onStart();
myTextView = (TextView) findViewById (R.id.textview1);
myTextView.setText ("Hello tablet!");
}
Which would result in a working APK with a single TextView in the top left showing "Hello tablet!" - my simple questions are two-fold...
If the project uses Fragments then should I fully expect that the UI components cannot be accessed in the onCreate() method of the Activity, as I see happening with lots of sample code on the Internet, probably because they've not been created yet?
Is it acceptable for me to be accessing UI components within the onStart() method of the Activity (which does work) - or should I be doing something else? Previously I have also used the onCreateView method of the Fragment, but is the best place to access UI components inside a Fragment actually in the onCreate() method of the Fragment, which I have not yet tried?
I also note that the onCreate() method of the default Fragment Android Studio creates for you when you create a new project does not have a stub provided... but onCreateView does, and the lifecycle documentation implies (to me, anyway) that this might be the best place to be doing things like this.
Any guidance on this is appreciated.
The fragment transaction commit command only puts the transaction in Que - the transaction will be processed some time in the future. This is why you couldnt use it straight from on create.
I suggest you to use fragments to encapsulate their Ui behavior - I wouldn't change a fragment's Ui elements explicitly from the activity.
OnCreateView inside a fragment is a good place to init the fragment's Ui elements since it is called when the fragment view is created.
Working with fragments is quite tricky and painful at start but From my experiance they do help to create a much more modular code.
I suggest you read more about fragments in the docs before starting a serious project with them.
http://developer.android.com/guide/components/fragments.html
#chipopo is right in the diagnostics and in the encapsulation recommendations.
But in case you need you can call FarmentManager.executePendingTransactions() after commiting the transaction. This ensures that the operations on fragments queue are execute synchronously.
I used that sometimes in non UI retainedInstance fragments.
Related
I have 3 fragments that are contained within the main activity via a ViewPager. What I'm trying to do is allow myself to call methods on objects of those fragment classes from different lifecycle callbacks in the main activity, namely onRestart(), so that I can refresh their content via a backend call. To be more specific, one of the fragments contains a "live feed" of images that people are posting, and the user gets to this posting activity via one of the fragments.
Right now, the problem I'm trying to solve is that when a user goes to this posting activity and puts up a picture, the live feed in the fragment they come back to isn't getting refreshed. To do this, I would need to call my backend and UI update methods in that fragment from the main activity's onRestart().
But I'm running into these null pointer exceptions -- here's what the code looks like in the fragment:
public void refreshUI(){
activity = getActivity();
appContext = ThisApp.appContext();
view = getView();
peopleHeader = (TextView) view.findViewById(R.id.people_header);
peopleLinearLayout = (LinearLayout) view.findViewById(R.id.local_people_linearlayout);
..... (various other instantiations)... }
The NPEs are coming either from getActivity() or some of the UI instantiations. I call this code from onActivityCreated() and it works as expected. However, if I try to call this code again on my fragment object from onRestart(), I crash.
What's happening? Can't figure out why this would go bad on me.
Thanks so much.
When your Activity is destroyed and recreated, your Fragments are also destroyed and recreated with it. You can have a look at the Fragment/Activity lifecycle explanation here.
What this means is that the reference you keep disappears, the Fragment is there but it is another object. You need to get a reference to this new object.
You can check this answer on how to do that. It explains getting references to Fragments created by a ViewPager adapter.
I have a button that I want to change its value often, so my Activity has a private variable :
private Button p1_button = (Button)findViewById(R.id.firstbut);
This simple line makes my app crash. If I put inside the onCreate it works and I can interact with the button (change text etc).
EDIT : I think I found the reason. I should initialize AFTER setcontentview ?
EDIT: Thank you for the constructive answers. I have now a different problem I removed the initialization and I did it on onCreate and it works (But I keeped the p1_button declaration as a private field). But when I tried to modify the button in a different method of my activity (just changing the text), it crashes again. So the return value of findViewById is "local" to the method where it is called and I should setcontentview in every method that access UI elements ?
Do not call findViewById() until after you call setContentView(). Otherwise, the widget will not exist.
More generally, do not call inherited methods on Activity until after super.onCreate(), unless specifically advised to do so.
It depends where you are calling this line.
http://i.stack.imgur.com/6EQaU.png
The onCreate() method contains a call to setContentView() and before this is called, Android has no idea what to do with your button as it hasn't been inflated yet!
Therefore as a really easy rule of thumb, always make sure setContentView (or if you're dealing with fragments onCreateView()) have been called and completed. Only then will findViewById() work.
If you would like further guidance, please post some code in which the crash occurs.
edit: I tried to add the image properly but don't have enough rep.
To understand this you need to know the Activity lifecycle. You are trying to look a view which has not yet been created by Android.
As per the android lifecycle explained here "http://developer.android.com/training/basics/activity-lifecycle/starting.html". In onCreate() method the activity is created and you can access different views of the activity. If you will try to look for view before onCreate() the app will crash as it does not know whether that view exists or not.
I can't for the life of me figure out why sometimes my Fragments become uncontrollable after my app has been launched, paused, and then resumed.
I hold references to my Fragments like this:
public class MainActivity ... {
public static AccountFragment accountFragment;
....
#Override
protected void onCreate( ... {
accountFragment = new AccountFragment(this);
...
}
I have a custom toolbar with a Spinner which theoretically should allow the user to cause an AsyncTask in the AccountFragment to run. This is implemented like so:
if (accountFragment.getView() != null) {
accountFragment.load()
}
I then create a ViewPager, a FragmentPagerAdapter, etc.
This works completely fine most of the time. The user can select a new item in the Spinner and the AccountFragment will update accordingly. However, sometimes after the app has been stopped and then later resumed, the AccountFragment will not respond at all to anything. Even other views in the app which should affect the AccountFragment are useless. It makes no sense to me because the AccountFragment's view cannot be null, and the AsyncTask which is executed by accountFragment.load() makes some very simple changes to the UI in its onPreExecute which should be very apparent (e.g. Everything disappears and a ProgressBar appears) which is simply not happening.
Any ideas?
There's not enough code to know exactly what's going wrong here, but there are also a bunch of stuff you're doing wrong.
1) Don't store fragment reference in a public static field. Make it just private, or protected at most. Read basic Java manuals for explanation.
2) Don't overload Fragment's constructor, it's discouraged by design. If you need to pass some values to it, do it by using arguments.
3) I guess you're not performing a check during onCreate() method in your Activity if it's being recreated or freshly created. You could end up with two instances of AccountFragment where one of them is in some weird detached state.
...
You should probably spend more time researching basics about Fragment/Activity relationship and how to avoid situations like this.
What you need is override these methods:
onSaveInstanceState(){}
onCreate (Bundle savedInstanceState){}
or onCreateView()
See http://developer.android.com/reference/android/app/Fragment.html#onSaveInstanceState(android.os.Bundle) . You put all the necessary parameters to bundle in onSaveInstanceState() and restore them in onCreate() or onCreateView().
I have some problems initializing some values for some views in my Android Activities cause for me to be able to get the views I have to wait for the activity layout aswell as the fragment layout to be inflated until I can initialize the views values. I know i can initialize the views in the fragments OnCreateView but I would like to avoid that and instead have some kind of method that is run right after the onCreates/onCreateView's are done.
Is this possible if so how? And what is the best practice of modifying/initializing views?
Thanks in advance
Unless you have a special case you should not deviate from the lifecycle of a Fragment or Activity.
Anyway if so, Calling a method as you asked for, look at the MainActivity class on this example. It uses a Handler to do work on the UI Thread
You could do this in the onStart() or onResume() methods, which are called after onCreate(), but also after stopping or pausing of the Activity.
For more detailed Information have a look at the Activity Documentation
You should also try to work on the formatting of your question, I'm not sure whether I understand the problem correctly, or not.
You can use the way mentioned over here. It's cleaner.
you put the following method in the fragment.
public void initFragment() { //Do your required things here }
After creating the object for the fragment (for instance example given below)
mFragmentManager = getSupportFragmentManager();
mIssDetailsListFragment = (IssDetailsListFragment) mFragmentManager
.findFragmentById(R.id.fragment_outlet_list);
you call the method initFragment() (Sample code given below)
mIssDetailsListFragment.initFragment()
There are two classes. MainActivity, in which i set the view, and ClassX from which i want to update a view in MainActivity. ClassX is an AsyncTask called from MainActivity, if that's relevant.
What i want to do is to change the text of a view called mainTextLog. I've declared a global TextView variable, and in the onCreate() method i set it to the view using findViewById().
private TextView logger;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
logger = (TextView) findViewById(R.id.mainTextLog);
}
By now i should be able to change the text from onCreate(), and i can. But since i want to change it from another class (ClassX) i need to create a method for it:
public void setLogText(String text) {
logger.setText(text);
}
But it doesn't work. I've tried making logger and the setLogText() method static, but it still doesn't work. The app just crashes.
It's probably pretty easy, but i'm out of ideas.
If you are using an AsyncTask you need to set the value in either onProgressUpdate or in onPostExecute.
You really should read the documentation for AsyncTasks
You CANNOT update the UI from the doInBackground method as it is not run in the UI thread and will give you an exception.
Also, you should post the exception you are getting when the application crashes so we have a better idea what the problem is. But I'd guess you are trying to update the text from the wrong thread.
I've done this plenty in the app im working on, its sort of an MDI type app on the android tablet.
To do what you're asking....
in MainActivity have
public static void setText(String txt){
((TextView)findViewById(R.id.mainTextLog)).setText(txt);
}
then in the child (or calling class) call it like...
MainActivity.setText("myTextToShow");
That's it... im using android api level 12... If i remember correcty it worked in api level 7 as well though.
Hope this helps...
One possibility is that: when you call setLogText in another Class X. The MainActivity may not be existing anymore, which makes the logger a null reference?