I write a custom dialog and try to get some data from its parent activity, but I always get null when I call getOwnerActivity, could anyone tell me why this happen? Why I can show the data in the DemoDialog while failed to show data from TestDialogActivity?
Many thanks in advance.
DialogTestActivity
public class DialogTestActivity extends Activity {
List<String> data = new ArrayList<String>();
Button button;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
showDialog(0);
}
});
}
public List<String> getData(){
data.add("one");
data.add("two");
data.add("three");
return data;
}
public Dialog onCreateDialog(int id){
return new DemoDialog(this);
}
}
DemoDialog
public class DemoDialog extends Dialog {
Context context;
public DemoDialog(Context context) {
super(context);
setContentView(R.layout.dialog);
this.context = context;
setTitle("Delete City");
ListView list = (ListView)findViewById(R.id.list);
ArrayAdapter<String> aa = new ArrayAdapter<String>(context, android.R.layout.simple_list_item_multiple_choice, ((DialogTestActivity)getOwnerActivity()).getData());
// ArrayAdapter<String> aa = new ArrayAdapter<String>(context, android.R.layout.simple_list_item_multiple_choice, getData());
list.setAdapter(aa);
list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
private List<String> getData(){
List<String> data = new ArrayList<String>();
data.add("1");
data.add("2");
return data;
}
}
I tried to use getOwnerActivity() method in all possible methods of my custom Dialog. It always returns null (Android 2.3). Then I checked its source code and the activity it returns is set only in setOwnerActivity(Activity activity) which is not called anywhere. So if you want getOwnerActivity() to return value different than null, you have to do this:
public MyCustomDialog(Context context) {
super(context);
if (context instanceof Activity) {
setOwnerActivity((Activity) context);
}
}
If you think about the situation, you will understand why. When you call new DemoDialog(this), you execute all the code in the constructor. After that, you return it from onCreateDialog and Android does its magic. If you try to get owner from the constructor, Android hasn't hooked it yet, so you have no owner yet.
So you can do either of these:
public class DemoDialog extends Dialog {
public DemoDialog(Context context) {
super(context);
// chances of context not being an activity is very low, but better to check.
Activity owner = (context instanceof Activity) ? (Activity)context : null;
if (owner != null) {
// owner activity is defined here
}
}
public void onAttachedToWindow() {
super.onAttachedToWindow();
// getOwnerActivity() should be defined here if called via showDialog(), so do the related init here
Activity owner = getOwnerActivity();
if (owner != null) {
// owner activity defined here
}
}
}
Note that the second method is preferred because
You can extends your custom dialog from AppCompatDialog and access to activity by this code:
public static Activity scanForActivity(Context context) {
if (context == null)
return null;
else if (context instanceof Activity)
return (Activity) context;
else if (context instanceof ContextWrapper)
return scanForActivity(((ContextWrapper) context).getBaseContext());
return null;
}
This, below, worked for me.
private Activity activity;
public MyCustomDialog(Activity activity) {
super(activity);
this.activity = activity;
}
Then I use activity instead of getOwnerActivity().
Related
I have a question regarding this simple frequently occurring situation in android .
I have an activity that will invoke the async task and async task will draw values from SQLite database and update on the UI. I used Async task to make the UI reponsive and fast.
This is the code I have been working on.
SqlHandler sqlHandler;
#BindView(R.id.list) ListView listView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity1);
ButterKnife.bind(this);
sqlHandler = new SqlHandler(this);
new DisplayAll(this).execute();
listView.setOnItemClickListener((AdapterView<?> parent, View view,
int position, long id) -> {
Intent i = new Intent(getApplicationContext(), Activity2.class);
String text = textView.getText().toString();
startActivity(i);
});
}
private class DisplayAll extends AsyncTask<Void, Void, Void> {
int null_val;
final ArrayList<listRow=> myList = new ArrayList<>();
private WeakReference<Activity> mActivity;
public DisplayAll(Activity activity) {
mActivity = new WeakReference<>(activity);
}
#Override
protected Void doInBackground(Void... params) {
myList.clear();
String query = " ...";
Cursor c1 =sqlHandler.selectQuery(query);
if (c1 != null && c1.getCount() != 0) {
if (c1.moveToFirst()) {
do {
.....
} while (c1.moveToNext());
}
}
try {
null_val = Objects.requireNonNull(c1).getCount();
c1.close();
}
catch (NullPointerException e)
{
Log.e("NPE", "" + e);
}
return null;
}
#Override
protected void onPostExecute(Void param) {
// get a reference to the activity if it is still there
Activity activity = mActivity.get();
if (activity == null || activity.isFinishing()) return;
ProgressBar prgBar=findViewById(R.id.prgbar);
listAdapter Adapter;
prgBar.setVisibility(View.GONE);
Adapter = new listAdapter(getApplicationContext(), myList);
listView.setAdapter(Adapter);
}
}
I had checked this question also.I have added the weak reference to my class now. But still Android Studio warns me about the memory leak.
I tried to change it to static, but changing the sqlhandler as static also causes memory leak. To change the async task to a top-level class is not good for me. I have many async tasks in different activities.
So anyone have any idea how to tackle this?
Hi there I'm thinking about what is the correct and best way to handle Activity, Fragment, AsyncTask and DialogFragments together.
My current state is that I start my Activity and replace its ContentView with my Fragment, in which I got an EditText and one Button.
Tapping my Button executes an AsyncTasks which Requests random things and takes some time. Meanwhile I display a DialogFragment begging for patience.
Desired behavior is that, e.g. I rotate my screen my DialogFragment keeps being displayed for the time my AsyncTask is running. After that I want to show up a simple toast displaying the information I got from my HttpRequest.
Compact overview about how I thought it would work:
BaseFragment keeps a WeakReference to the Activity it's attached to
AsyncTask keeps a WeakReference to Fragment which exectures it
AsyncTasks onPreExecute() shows up the DialogFragment
AsyncTasks onPostExecute() dissmisses the DialogFragment
BaseFragment holds DialogFragment
Unfortunately this is not the way it works, on orientation change my DialogFragment keeps being displayed and no toast is showing up.
What am I doing wrong ?
public class BaseFragment extends Fragment{
private static final String TAG = BaseFragment.class.getSimpleName();
protected WeakReference<AppCompatActivity> mActivity;
private TemplateDialogFragment dialogFragment;
public WeakReference<AppCompatActivity> getAppCompatActivity(){ return mActivity; }
#Override
public void onAttach(Context context) {
if(!(context instanceof AppCompatActivity)) {
throw new IllegalStateException(TAG + " is not attached to an AppCompatActivity.");
}
mActivity = new WeakReference<>((AppCompatActivity) context);
super.onAttach(context);
}
#Override
public void onDetach() {
mActivity = null;
super.onDetach();
}
#Override
public void onStart() {
super.onStart();
showContent();
}
public void showContent(){
}
public void showDialog(String title, String content){
dialogFragment = new TemplateDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(TemplateDialogFragment.DIALOG_TITLE, title);
bundle.putString(TemplateDialogFragment.DIALOG_MESSAGE, content);
dialogFragment.setArguments(bundle);
dialogFragment.show(getFragmentManager(), TemplateDialogFragment.FRAGMENT_TAG);
}
public void notifyTaskFinished(String result) {
dismissDialog();
if(mActivity != null && !mActivity.get().isFinishing()) {
Toast.makeText(mActivity.get().getApplicationContext(), result, Toast.LENGTH_LONG).show();
}
}
private void dismissDialog(){
if(dialogFragment != null && dialogFragment.isAdded()) {
dialogFragment.dismissAllowingStateLoss();
}
}
}
...
public class TemplateFragment extends BaseFragment {
private static final String TAG = TemplateFragment.class.getSimpleName();
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.test_fragment, container, false);
}
#Override
public void showContent() {
super.showContent();
Button startTask = (Button) getAppCompatActivity().get().findViewById(R.id.button0);
final BaseFragment instance = this;
startTask.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
CustomAsyncTask task = new CustomAsyncTask(instance);
EditText input = (EditText) getAppCompatActivity().get().findViewById(R.id.text0);
task.execute(input.getText().toString());
}
});
}
private static class CustomAsyncTask extends AsyncTask<String, Void, String> {
WeakReference<BaseFragment> weakBaseFragmentReference;
private CustomAsyncTask(BaseFragment fragment) {
weakBaseFragmentReference = new WeakReference<>(fragment);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
weakBaseFragmentReference.get().showDialog("Executing", "Working on the request...");
}
#Override
protected String doInBackground(String... params) {
HttpURLConnection con = HttpUrlConnectionFactory.createUrlConnection("https://www.httpbin.org/bytes/" + (params[0] == null ? "1" : params[0]));
return HttpRequester.doGet(con).getResponseAsString();
}
#Override
protected void onPostExecute(String response) {
super.onPostExecute(response);
if(weakBaseFragmentReference.get() == null) {
return;
}
weakBaseFragmentReference.get().notifyTaskFinished(response);
}
}
}
*Edit:
After some time researching this theme I'm sure a Service is the best solution for most of my field of use. Also I used AsyncTaskLoaders a lot, because there is a smooth control of lifecycle....
Use progress bar instead of DialogFragment.
AsyncTask should only be used for tasks that take quite few seconds.
AsyncTask doesn't respect Activity lifecycle, and can lead to memory leaks.
Check some gotchas.
You can try AsyncTaskLoader to survive configuration changes.
I'd like to show an AlertDialog (with a couple of Radio Button buttons and an OK button) as soon as a fragment is created.
Where is the best place to call the dialog fragment? I have tried in onViewCreated() and on onResume() and both work, but I am not sure what's best practice.
Also, to ensure the dialog isn't shown every time the fragment is stopped/recreated due, for example, to screen rotation, I have created Boolean value called mShowDialog and set it to 'true' in onCreate() then used an 'If' statement to decide whether the dialog should be shown (see below for example).
onCreate(){
//....
mShowDialog = true;
}
onResume(){
if (mShowDialog){
//....show dialog code
// set mShowDialog to false to ensure code executed only once
mShowDialog = false;
}
}
Is the above code the best way of fulfilling both requirements?
Btw, I am fairly new to programming.
Thanks in advance for the help.
Best practice for this is to inflate dialog in onCreateView() method of fragment.
If you're trying to create it from the activity adding the fragment, I've had good luck with adding a FragmentListener to my fragments and setting it from the activity. This is my basic BaseFragment class that all my fragments extend:
public class BaseFragment extends Fragment {
public Context context;
public Activity activity;
public FragmentListener fragmentListener;
private boolean attached = false;
public BaseFragment() {
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!isAttached()) {
this.context = activity;
this.activity = activity;
if (this.fragmentListener != null) {
this.fragmentListener.onAttached();
}
setAttached(true);
}
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (!isAttached()) {
this.context = context;
this.activity = (Activity) context;
if (this.fragmentListener != null) {
this.fragmentListener.onAttached();
}
setAttached(true);
}
}
#Override
public void onDetach() {
super.onDetach();
setAttached(false);
if (this.fragmentListener != null){
this.fragmentListener.onDetached();
}
}
public void setFragmentListener(FragmentListener fragmentListener) {
this.fragmentListener = fragmentListener;
}
public View.OnClickListener onBackTapped = new View.OnClickListener() {
#Override
public void onClick(View v) {
getActivity().onBackPressed();
}
};
public boolean isAttached() {
return attached;
}
public void setAttached(boolean attached) {
this.attached = attached;
}
public boolean isPermissionGranted(String permission){
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
}
public boolean ifShouldShowRationaleForPermission(String permission){
return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
}
public void showPermissionRequest(Activity activity, int requestCode, String... permissions){
ActivityCompat.requestPermissions(activity, permissions, requestCode);
}
}
This way, I can do this in my activity:
MyFragment myFragment = new MyFragment();
myfragment.setFragmentListener(new FragmentListener() {
#Override
public void onAttached() {
// Stuff I want to do when it is attached
}
#Override
public void onDetached() {
// Stuff I want to do when it is detached
}
});
fragmentManager.beginTransaction()
.add(R.id.container, myFragment)
.commit();
And then I can add whatever code I want when the fragment does it's various stuff.
Good luck!
Like my title says, i'm looking for an equivalent of getActivity() in my ActionBarActivity class in my Android project.
I want to pass an Activity parameter in AsyncTask declaration object, because i'm using an Activity object in my custom AsyncTask extended class
Here an example simplest code of my project
public class EventCreator extends ActionBarActivity {
private Context context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_even_creator);
View v = getLayoutInflater().inflate(R.layout.activity_even_creator,null);
this.context = this.getBaseContext();
final Button createButton = (Button)findViewById(R.id.createEventButton);
createButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
AsyncTask<Void,Void,Boolean> eventCreatorSend = new SendEvents(/* here need activity object */);
eventCreatorSend.execute();
}
});
}
class SendEvents extends AsyncTask<Void,Void,Boolean> {
public Activity act;
SendEvents(Activity a) {
this.act = a;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
((LinearLayout)act.findViewById(R.id.layout_loader_create_event)).setVisibility(View.VISIBLE);
}
#Override
protected Boolean doInBackground(Void... params) {
SystemClock.sleep(5000);
return true;
}
#Override
protected void onPostExecute(Boolean params) {
if (params){
((LinearLayout)act.findViewById(R.id.layout_loader_create_event)).setVisibility(View.GONE);
act.finish();
}
else {
((LinearLayout)act.findViewById(R.id.layout_loader_create_event)).setVisibility(View.VISIBLE);
Toast.makeText(act,"Fail to send event",Toast.LENGTH_SHORT).show();
}
}
};
}
In a time, i thought use getParent() from ActionBarActivity class, but it return a null object.
So how to get the Activity object i want in ActionBarActivity class ?
Try nameofactivity.this instead getActivity()
I always use getActivity() in Fragments Activities and .this in any other kind of Activity.
Oh damn !
I just found a solution just after posting my ask.
I use MyClass.this, and it's done. Like this :
AsyncTask<Void,Void,Boolean> eventCreatorSend = new SendEvents(EventCreator.this);
eventCreatorSend.execute();
Hope that's can help someone !
The easiest way is an Activity variable
// in your fragment
Activity myActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
//fragment shouts "i know my father!!!"
myActivity = activity; //
}
now your activity instance(The father) can be represented anywhere
On the event invocation of an activity, I opened an AlertDialog.Builder which lists an array of single choice items. When the user clicks any item, I want to set the same to a text view in the activity.
I tried this:
Activity class:
public MyActivity extends Activity implements onClickListener {
TextView item;
public void onCreate(Bundle state) {
super.onCreate(savedState);
setContentView(R.layout.main);
item = (TextView) findViewById(R.id.id_item);
item .setOnClickListener(this);
}
public void onClick(View v) {
new MyBuilder(this).show();
updateUI();
}
private void updateUI() {
item.setText(ItemMap.item);
}
}
Builder class:
public class MyBuilder extends AlertDialog.Builder implements OnClickListener{
Context context;
String[] items = {"pen", "pencil", "ruler"};
public MyBuilder(Context context) {
super(context);
super.setTitle("Select Item");
this.context = context;
super.setSingleChoiceItems(items, 0, this);
}
#Override
public void onClick(DialogInterface dialog, int position) {
ItemMap.item = items[position];
dialog.dismiss();
}
}
Mapping class:
public class ItemMap {
public static String item;
}
Here, MyBuilder is a subclass extending AlertDialog.Builder
updateUI() tries to set the value which user chooses from the list of items. But it did not work! updateUI() is called soon after the dialog is shown.
Could anyone help me out?
Thanks in advance!
With updateUI() in the current location, you are trying to access ItemMap.item before it is set in the AlertDialog.Builder. You're going to need some way to call back from the onClick in the AlertDialog.Builder to your main class - I would do it by adding an interface and then passing that in to your builder - like this:
Activity class:
public MyActivity extends Activity implements onClickListener, AlertBuilderCallback {
TextView item;
public void onCreate(Bundle state) {
super.onCreate(savedState);
setContentView(R.layout.main);
item = (TextView) findViewById(R.id.id_item);
item .setOnClickListener(this);
}
public void onClick(View v) {
new MyBuilder(this).addCallback(this).show();
updateUI();
}
public void updateUI() {
item.setText(ItemMap.item);
}
}
AlertBuilderCallback interface:
public interface AlertBuilderCallback {
public void updateUI();
}
Builder class:
public class MyBuilder extends AlertDialog.Builder implements OnClickListener{
Context context;
String[] items = {"pen", "pencil", "ruler"};
public MyBuilder(Context context) {
super(context);
super.setTitle("Select Item");
this.context = context;
super.setSingleChoiceItems(items, 0, this);
}
public MyBuilder addCallback(AlertBuilderCallback callBack) {
this.callBack = callBack;
return this;
}
#Override
public void onClick(DialogInterface dialog, int position) {
ItemMap.item = items[position];
if(this.callBack != null) {
this.callBack.updateUI();
}
dialog.dismiss();
}
}
Mapping class:
public class ItemMap {
public static String item;
}
move the updateUI() from MyActivity onClick(), to onClick for Dialog.
#Override
public void onClick(DialogInterface dialog, int position) {
ItemMap.item = items[position];
updateUI();
dialog.dismiss();
}
You're doing a load of things wrong here. You could move the updateUI() in to the onClick in your Activity, which should work, but here's another few things to think about:
Why is your AlertDialog.Builder in a different class? this is alright if you are going to extend it with some extra behaviour and use it in other places in your application - if if you are only using it here then you should declare it inside your activity.
Why is your ItemMap.item static? Is that a design decision?