Splash Screen with AsyncTask - java

I want to implement a splash screen into my app. I already did this. But at the moment it just waits 3 seconds and then calls the MainActivity class. The problem with that is that i have data to load and with the current setup the user have to waits two times. I want a splash screen that loads all the data. I have a MainActivity class where everything happens and I have my SplashScreen class.
The method I want to be run in the background is in my MainClass. So basically I have my splash screen class like that
public class SplashScreenActivity extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
return null;
}
protected void onPostExecute(Void... params) {
// TODO Auto-generated method stub
}
protected void onCancelled() {
// TODO Auto-generated method stub
}
}
I also implemented but not copied the imports and packages, so that shouldn't be a problem. Now, if I understood correctly I need to write the task that should be done into the doInBackground method. So I basically have to call the method from my other activity class, right?
public MainActivity mA = new MainActivity();
and then in the method I would write mA.parseXMLfromURL();
And afterwards I would start an intent of the main class into the onPostExecute-method like this?
protected void onPostExecute(Void... params) {
Intent mainClass = new Intent(SplashScreenActivity.this, MainActivity.class);
startActivity(mainClass);
}
If more information is needed I will gladly elaborate further.
UPDATES
Well, after your comments I tried it a bit differently.
This is my oncreate method
public void onCreate(Bundle savedInstanceState) {
StrictMode.setThreadPolicy(policy);
super.onCreate(savedInstanceState);
sv = new ScrollView(this);
layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
sv.addView(layout);
setContentView(sv);
new SplashScreenActivity().execute("URL TO FILE");
}
And this is the SplashScreenActivity
public class SplashScreenActivity extends AsyncTask<String, Void, Void> {
public MainActivity mA = new MainActivity();
protected void onPostExecute(Void... params) {
}
protected void onCancelled() {
// TODO Auto-generated method stub
}
#Override
protected Void doInBackground(String... params) {
mA.parseXMLfromURL(params);
return null;
}
}
But this just returns a blank screen. However if I call the parseXMLfromURL in my main activity it works just fine.
#Raghunandan said in comments that I wrongly created the instance of the class. Would be glad if you could elaborate your answer.
UPDATE NUMBER TWO
Current SplashScreen-Code is the following:
package de.activevalue.T1000flies;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
public class SplashScreenActivity extends Activity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash_screen);
new mTask().execute();
}
private class mTask extends AsyncTask<Void, Void, Void>{
MainActivity mA = new MainActivity();
#Override
protected Void doInBackground(Void... arg0) {
mA.parseXML();
return null;
}
protected void onPostExecute(Void... params){
Intent i = new Intent(SplashScreenActivity.this, MainActivity.class);
startActivity(i);
finish();
}
}
}
With that code the app just stucks at the Splash-Screen. It seeems like there is a problem with my XML-Parsing. Here is the XML-Parsing-code. Note that it works without any problems, when I just start the main activity wihtout the splash screen
UPDATE NUMBER THREE
I just started to debug by making breakpoints line per line. It jumps out at this line
rankingDate [k] = new TextView(this);
Rest of the code
for(int k = 0; k < metaList.getLength(); k++){
Node metaNode = metaList.item(k);
System.out.println(metaList.getLength());
rankingDate [k] = new TextView(this);
rdate [k] = new TextView(this);
numberOE [k] = new TextView(this);
Element metaElement = (Element) metaNode;
NodeList rankingDateList = metaElement.getElementsByTagName("date");
Element rankingDateElement = (Element) rankingDateList.item(0);
rankingDateList = rankingDateElement.getChildNodes();
rankingDate [k].setText("RankingDate: " + ((Node) rankingDateList.item(0)).getNodeValue());
layout.addView(rankingDate[k]);
xmlSerializer.startTag(null, "date");
xmlSerializer.text(((Node) rankingDateList.item(0)).getNodeValue());
xmlSerializer.endTag(null, "date");
}
The system.out.println gives me 1. And k is 0. So why is it a Null Pointer Exception?

You should create new activity for SplashScreen --> SplashScreenActivity extends Activity, declare in manifest and than set layout ;
public SplashScreenActivity extends Activity{
protected void onCreate(Bundle ...){
super.onCreate(...);
setContentView(...);
new mTask().execute();
}
private class mTask extends AsyncTask<Void, Void, Void>{
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
return null;
}
protected void onPostExecute(Void... params) {
Intent mainClass = new Intent(SplashScreenActivity.this, MainActivity.class);
startActivity(mainClass);
finish();
}
}
}

Piggy-backing off nurisezgin's answer explaining how AsyncTasks work in android, you're almost there but need to get some other things out of the way.
First: In Android-world, you never initialize activities by calling their constructor. Activities are handled by the operating system, and you run them using Intents.
That out of the way, you're very close to having your issue solved. You need to take whatever code is in the parseXML function of your MainActivity and put it either into your SplashScreenActivity and call it, or just put it directly in your doInBackground method.
Your doInBackground method should not be calling any outside activities.

An easier way to do your splash screen is to have it appear in front of your main activity that needs to load data using Dialogs. The easiest way to go about this is to override the onPreExecute() method in AsyncTask. The following is a simple example of a splash screen.
MainActivity.java
public class MainActivity extends Activity
{
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new SplashScreen(this).execute();
}
}
SplashScreen.java
public class SplashScreen extends AsyncTask<Void, Void, Void>
{
Context ctxt;
Dialog splash;
public SplashScreen(Context ctxt)
{
this.ctxt = ctxt;
}
protected void onPreExecute()
{
splash = new Dialog(ctxt, R.style.full_screen);
splash.show();
}
protected Void doInBackground(Void... ignore)
{
//Fetch stuff
return null;
}
protected void onPostExecute(Void ignore)
{
splash.dismiss();
}
}
In your res/values/styles.xml file, you want to put the following xml for full screen.
<!-- Stuff that's already in here -->
<style name="full_screen" parent="android:Theme.Light">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
<!-- Stuff that's already in here -->
This will give you a very basic splash screen (i.e., a blank screen that says nothing). If you look into the Dialogs API, you can find other ways to customize it that allow you to use pictures instead of text as well as completely customize the layout of the Dialog. Also look into DialogFragments if you want an even further customization. The benefit of doing your splash screen this way is that you can retrieve all of your information and set it up in the onPostExecute() to your MainActivity without having to worry about transferring the data.
Hope this helps! Good luck.

I tried exactly the same in a project, but my approach was different. Maybe it`could solve your problem as well...
first, my SplashScreen was an overlay in the MainActivity
//main-activtiy xml
<RelativeLayout ...
<RelativeLayout id="overlay" visible="true"... //filled parent and had a centered image
<RelativeLayout id="mainActivity" visible="false"... //the application
Application launched
Start your AsyncTask or Service in your onCreateMethod
if data is loaded, send Notification to your MainActivity and "swap your view" like
public void functionCalledOnDataLoaded(){
//do you init stuff
((RelativeLayout) findViewById(R.id.overlay)).setVisibility(View.INVISIBLE);
((RelativeLayout) findViewById(R.id.mainActivity)).setVisibility(View.VISIBLE);
}

Why don't you only create MainActivity. The Splash is just a frame layout of main.xml, and will be setVisibility(View.GONE) after certain time.
Using this method, you have only 1 activity. Thus, it's easier to handle load data from network without interrupt.

Related

Accessing data of destroyed activity means I have a memory leak?

I've created an interface which holds a reference to an interfaces instantiated from an activity.
This is the interface:
public interface Calback {
void fun();
}
This is the activity which instantiates the calback and binds it to asincktask.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView txt = findViewById(R.id.helloTxtv);
txt.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Calback call = new Calback() {
#Override
public void fun() {
Log.d("tag","text of destroyed activity: "+((TextView)findViewById(R.id.helloTxtv)).getText());
}
};
Worker worker = new Worker(call);
worker.execute();
}
});
}
}
What's strange is that using that calback I can access textview even if the activity was destroyed.
This is the code from asyncktask:
public class Worker extends AsyncTask<Void, Void, Void> {
private final Calback call;
public Worker(Calback call) {
this.call = call;
}
#Override
protected Void doInBackground(Void... voids) {
try {
sleep(5000);
Log.d("tag","done");
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
call.fun();
}
}
To ensure that the activity it's destroyed I've just rotated the screen.(But I've got the same result after starting another activity and finish the current one)
And here is the log result.
PS: I've used Android Studio 3.0
If you are able to access the text of the TextView after the parent Activity has been destroyed, then you have a memory leak.
However, I'm not convinced that is what is going on here. I think it is more likely that either the activity has not been destroyed, or the activity's state was persistent and you are now looking at the state in the new (reincarnated) activity.
Why? Because, it seems that the callback is being called via an onClick listener for the text view. And that can only occur if the specific text view is still visible. It can't be visible if it is a component of a destroyed activity.

Asynctask onpostexecute is not updating views if the screen is rotated while doinbackground is excuted

I had a simple program where i need to update the list and text based on the server response ...
But Asynctask onpostexecute is not updating views if the screen is rotated while doinbackground is executed .
I came to know the reason that , as the activity is recreated , onpostexecute wont update its views (Same problem..here is the link : Chek this link)
But i was not satisfied with the answer as it just suggesting to restricting to recreate the activity (i want recreating the activity as in my project i had some extra layout in landscape mode).
Please dont suggest setretaininstance(true) by taking fragments as it doesnt call oncreateview(), which is not suitable for my project.
May be as lastoption i can restrict orientation programatically in onpreexecute and release it in onpostexecute. But still it will not be good practice i think.
public class MainActivity extends ActionBarActivity {
TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView=(TextView) findViewById(R.id.textview);
if(savedInstanceState==null)
{
new myAsync().execute();
}
}
public class myAsync extends AsyncTask<Void, String, Void>
{
#Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
textView.setText("started");
Log.e("started", "started");
}
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#SuppressWarnings("deprecation")
#Override
protected void onPostExecute(Void result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
Log.e("executed", "executed");
}
}
}
This is my sample program . textview is not updating if screen is rotated.
Please suggest . Thanks in advance .
You could provide myAsyncTask with a TextView member with a setter and store the current task as static member of the activity.
class MyActivity extends Activity {
private static AsyncTask myTask = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.textview);
if (myTask == null) {
new myAsync(textView).execute();
} else if(myTask.getStatus() != AsyncTask.Status.FINISHED) {
myTask.set(textView);
}
}
private class myAsync extends AsyncTask<Void, String, Void>
{
TextView textView;
myAsync(TextView textView) {
this.textView = textView;
}
synchronized void setTextView(TextView textView) {
this.textView = textView;
}
...
}
}
You would still have to deal with race conditions. E.g. you would probably want to impelemnt a mechanism to pause/resume your task, whenever the activity pauses/resumes.
You'd also have to make sure that the tasks textView is still valid and that you cleanup your static task in onPostExecute.
You can use the concept of bundle to put some string in it. When the activity is recreated after rotation check if saved instance state is null or not in the oncreate method. If not null retrieve the string using the bundle and update the textview. For more information on this rotation thing check out the videos of slidenerd on YouTube in the asynctask and threads playlist. Hope it helps.

Cannot resolve symbol AlertDialogActivity

So I'm just trying to create an Alert Dialog that is just a message (no buttons or titles). I want to display an alert dialog when a background task is running. The alert dialog will run on the UI thread.
Here's what I have done so far:
protected void onPreExecute() {
super.onPreExecute();
AlertDialog altDlg;
altDlg = new AlertDialog.Builder(AlertDialogActivity.this).create();
altDlg.setMessage("Retrieving Information. Please Wait");
altDlg.show();
}
I also tried doing this:
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setMessage("Retrieve Info. Please Wait").show();
The error I am getting with the first one is:
cannot find symbol 'AlertDialogActivity'
symbol: class AlertDialogActivity
location: class com.example.Device.Activity
The second attempt error says:
incompatible types: com.example.Device.Activity cannot be converted to android.content.Context
I'm not sure what I am doing wrong in either scenario. I just want to display a basic message when a background task is running and I was hoping the closest thing I can use is AlertDialog.
EDIT for how to set up AsyncTask properly:
Small background of what I want to do. I just want to read in a file, deserialize it and save it's contents to a db.
Right now I'm assuming I only need two activities.
One is my main activity:
public class MainActivity extends Activity {
/**
* Called when the activity is first created.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.setup);
final Button setup_button = (Button) findViewById(R.id.setup_button);
setup_button.setOnClickListener (new View.OnClickListener() {
public void onClick(View view){
setContentView(R.layout.retrieve_info);
}
});
}
}
Now the onClick event just moves to the new view that is supposed to display the message or alert dialog that says retrieving information. Please Wait. It displays the message while reading a file and saving to db. Once the file is read and saved, The message should disappear and say something like setup complete.
My second activity so far is:
public class RetrieveInfoActivity extends AsyncTask<Void,Void,Void> {
private ProgressDialog progressBar;
private void retrieveInfo(String fileName) {
try {
File file = new File(fileName);
Scanner scanner = new Scanner(file);
//Read all the lines until there are no more lines
while (scanner.hasNextLine()) {
scanner.nextLine();
//TODO: deserialize and save to db
}
scanner.close();
}
catch (FileNotFoundException e) { e.printStackTrace(); }
}
#Override
protected Void doInBackground(Void... params) {
retrieveInfo("test.txt");
return null;
}
protected void onPreExecute() {
super.onPreExecute();
progressBar.setIndeterminate(true);
progressBar.setCancelable(false);
progressBar.setMessage("Retrieve Information.Please wait");
progressBar.show();
}
#Override
protected void onPostExecute() {
progressBar.dismiss();
}
}
That's all I really have so far. I just need to understand how to set up this in Android conceptually.
Hope this makes sense.
Try this:
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
Instead of using an AlertDialog use a ProgressBar, it will do the trick for you.
private ProgressDialog progressBar;
#Override
protected void onPreExecute() {
super.onPreExecute();
progressBar.setIndeterminate(true);
progressBar.setCancelable(false);
progressBar.setMessage("Your message");
progressBar.show();
}
#Override
protected void onPostExecute(final String error_code) {
progressBar.dismiss();
}
Looks like you are extending AsyncTask and trying to use it as a context. That won't work as AsyncTask itself is nothing but an abstract class.
You need to create a custom constructor for your AsyncTask to fetch the Context:
public class MyTask extends AsyncTask<Void, Void, Void> {
private Context mCtx;
public MyTask(Context context) {
mCtx = context;
}
...
Then when starting your AsyncTask, pass the context:
new MyTask(this).execute();
Another way would be to make the AsyncTask an inner class and use YourActivity.this when creating the dialog. Example:
public class YourActivity extends Activity {
...
private class MyTask extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
AlertDialog dialog = new AlertDialog.Builder(YourActivity.this).create();
}
#Override
protected Void doInBackground(Void... params) {
...
}
}
}

Adding AsyncTask class to activity crashes app

I recently needed to use AsyncTask in an activity in my android app. So I made a class inside the activity and extended AsyncTask in that class.
But now, whenever I launch that particular activity, my app immediately crashes. I tried putting the whole onCreate() of the activity in a try, catch block, but no exceptions were raised. The app just crashes immediately when I launch that activity, without any explanation in the LogCat.
This started happening after I added the AsyncTask class mentioned above. Also, the AsyncTask part is NOT executed when the activity is launched, it executes on pressing a button. What could be going wrong? I have added the relevant code below:
public class ListViewA extends Activity{
public void onCreate(Bundle savedInstanceState) {try{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView lv= (ListView)findViewById(R.id.listview);
//computation
}
private String[] top() {
new RetreiveFeedTask().execute(ctr,ctz);
return er;
}
public class RetreiveFeedTask extends AsyncTask<Context, Void, String[]> {
String[] q;
protected String[] doInBackground(Context... params) {
//computation
}
protected void onPostExecute() {
er=q;
gff=true;
}
}
EDIT:
I found an error in LogCat which looks important:
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.exampleapp/com.example.exampleapp.ListViewA}: java.lang.NullPointerException
Your edit is interesting and suggests that there is a problem in the instantiation of the Activity, in which case I would look at the variable declarations quite carefully. However, in the absence of any of that code (or more onCreate code), or the surrounding lines from the logcat, it's hard to be more specific.
As #Raghunandan says, the way your AsynTask is constructed is incorrect, it should be something like:
public class RetreiveFeedTask extends AsyncTask<Context, Void, String[]> {
String[] q;
#Override
protected String[] doInBackground(Context... params) {
//computation
return result; // result is of type String[]
}
protected void onPostExecute(String[] result) {
// do something with result ...
er=q;
gff=true;
}
}
A secondary point is your top() function, which returns er. I am guessing that you want it to wait until the AsyncTask is complete and then return er, which has been calculated by the AsyncTask. However, what it will do at present is to set the AsyncTask and then immediately return er: it won't wait for the AsyncTask to complete.
To achieve that, you probably need to split the button press action into two phases:
Create and run the AsyncTask
OnPostExecute in the AsyncTask calls the code which currently uses the er returned by top.
public class ListViewA extends Activity{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView lv= (ListView)findViewById(R.id.listview);
//computation
new RetreiveFeedTask().execute(ctr,ctz);
}
...
public class RetreiveFeedTask extends AsyncTask<Context, Void, String[]>{
#Override
protected String[] doInBackground(Context... fileName) {
//computation
String[] q;
//Know that fileName is an Array
return q
}
protected void onPostExecute(String[] result) {
//do something with the result
}
}
...
}
I have a private class ReadFilesTask extends AsyncTask<String, Void, String> in my program. Under that, I have: protected void onPreExecute(),
protected String doInBackground(String... params), and
protected Void onPostExecute(String result).
The first two have #Override above them. The only one I'm ACTUALLY using though, is doInBackground. I'd first really just try adding #Override to doInBackground(). Next, try changing Context to String.
EDIT:
You are also missing the function to grab an activity. Again, this is simply what I'm doing in my code and a few examples I saw online:
public RetreiveFeedTask(Activity activity) {
this.activity = activity;
}
Something like that.

Show splash screen until app is done loading

My app loads a lot of stuff on startup and after testing it delays too long at the beginning to not have a splash screen. So, I want to display a splash screen until my app is done loading. I do NOT want to display a screen with a timer for X seconds. I found an example here:
Android SplashScreen
I tried implementing the code in the SO topic above but I just don't understand the code. After integrating it in my code I come up with one error that I commented into the code below. But I don't understand a lot of the code and I have commented in the code below the parts I am confused by.
public class MainMenu extends Activity {
private ProgressDialog pd = null;
private Object data = null; //What is this?
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mainmenu);
// show the ProgressDialog on this thread
this.pd = ProgressDialog.show(this, "Working...", "Downloading data...", true, false);
// start a new thread that will download all the data
new DownloadTask().execute("Any parameters to download."); //What is DownloadTask()?
}
private class DownloadTask extends AsyncTask<String, Void, Object> {
protected Object doInBackground(String... args) { //Are these parameters correct?
return "replace this with your object"; //What is this?
}
protected void onPostExecute(Object results) {
// pass the resulting data to the main activity
MainMenu.this.data = result; //Error: "result cannot be resolved to a variable"
if(MainMenu.this.pd != null) {
MainMenu.this.pd.dismiss();
}
}
}
}
Let's start with the error:
MainMenu.this.data = result;
Notice the typo? It should be result*s*:
MainMenu.this.data = results;
Addressing the rest of your questions below:
private class DownloadTask extends AsyncTask<String, Void, Object>
The declaration is for an inline class called DownloadTask, and it states that you'll be taking Strings (via String...) as parameters to your doInBackground(String... params).
The second parameter (Void in your case) indicates the datatype used to "publish" the progress via publishProgress(DATATYPE)/onProgressUpdate(DATATYPE... progress). This method is suitable for notifying the user of changes, for example when you've finished downloading a file but still have a few to go.
The last parameter (Object), indicates what type of data you'll be passing on to onPostExecute(DATATYPE), in this example Object. This could either be to update a ListAdapter somewhere, or trigger any other UI change based on the outcome of the actions done in doInBackground.
Show ProgressDialog in onPreexecute and dismiss it in onPostExcute methods
something like this
private class DownloadTask extends AsyncTask<String, Void, Object> {
#Override
protected void onPreExecute() {
mProgressDialog = new ProgressDialog(activity);
mProgressDialog =ProgressDialog.show(activity, "", "Please Wait",true,false);
super.onPreExecute();
}
protected Object doInBackground(String... args) { //Are these parameters correct?
return "replace this with your object"; //What is this?
}
protected void onPostExecute(Object results) {
// pass the resulting data to the main activity
MainMenu.this.data = results; //it should be results
if (mProgressDialog != null || mProgressDialog.isShowing()){
mProgressDialog.dismiss();
}
if(MainMenu.this.pd != null) {
MainMenu.this.pd.dismiss();
}
}

Categories