Issue
I've got problem while sending email in background. I'm trying to put text set inside EditText as an email message, but there is always null coming to my mailbox, because AsyncTask is always called before i type anything inside EditText, and press "ok" at Dialog.
final int partsCount = imageKeeperList.size();
class PhotoSend extends AsyncTask <Void, Void, Void>{
@Override
protected void onPreExecute() {
}
@Override
protected Void doInBackground(Void... voids) {
final String username = "[email protected]";
final String password = "somepassword";
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.gmail.com");
props.put("mail.smtp.socketFactory.port", "465");
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.port", "465");
Session session = Session.getDefaultInstance(props,
new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username,password);
}
});
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress("[email protected]"));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("[email protected]"));
message.setSubject("Subject of email");
message.setText("Some text.");
Transport.send(message);
Log.d(TAG, "onInput: background");
} catch (MessagingException e) {
throw new RuntimeException(e);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
Toast.makeText(getContext(), "Sent.", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onInput: postExecute");
}
}
new MaterialDialog.Builder(getContext())
.content("Set description")
.inputType(InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE)
.input("short description", "", new MaterialDialog.InputCallback() {
@Override
public void onInput(MaterialDialog dialog, CharSequence input) {
if (input.length() == 0) partPicturesDescription = "No description";
else partPicturesDescription = dialog.getInputEditText().getText().toString();
dialog.dismiss();
Log.d(TAG, "onInput: preExecute");
}
}).show();
PhotoSend ps = new PhotoSend();
ps.execute(partPicturesDescription);
}
I had dialog inside my onPreExecute() method, but it stays the same, doInBackground goes first.
Solution
Let's see if I can help ya. So first you have a slight misuse of asyncTasks.
An AsyncTask is intended to do a short "Background" operation that returns the results to the UI Thread. The UI thread is the only thread that can touch UI elements without violation or crashing properly.
So the typical behavior would be
UI-Thread -> Get users input
AsyncTask (Background Thread) -> Send or handle data passed in
UI-Thread -> Notify user of success/failure
So you have a couple of options.
1) Do your UI dialog "Before" launching the AsyncTask
private void getUserInput(){
Session session = Session.getDefaultInstance(props,
new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
doBackgroundProcessing(new PasswordAuthentication(username,password));
}
});
}
private void doBackgroundProcessing(PasswordAuthentication passAuth){
if(passAuth == null || !passAuth.isSuccessful()){ //or whatever success flag they have
Log.e(TAG, "Failed to get credential token");
return;
}
//else we send it to the server it appears based on your code\
showBusyIndicator() //IF you need to block the UI from interacting while you send, NOTE* showBusyIndicator is just a method YOU would create to show one.
new PhotoSend()() {
@Override
public void onPostTask(Boolean wasSuccessful) {
//I recommend actually returning a valid result rather then Void so you know whether or not it succeeded.
if(wasSuccessful){
//close busy indicator, and notify of success
}else{
//close busy indicator, and notify of error
}
}
}
}
Your other option would be to move to Coroutines. These are very handy as you can use async and await to suspend your action.
Here is an example of waiting on a dialog to give back a name before moving forward.
protected suspend fun getNameFromDialog(): String? = suspendCancellableCoroutine { c ->
A35Log.v(mClassTag, "getNameFromDialog")
GetTextEntryDialog.newInstance(getParamsForTextDialog(), object : ITextEntryDialogListener {
override fun onTextEntered(text: String) {
if(!c.isCompleted) {
A35Log.v(mClassTag, "User entered name: $text")
c.resume(text)
}
}
override fun onCancel(){
if(!c.isCompleted) {
A35Log.v(mClassTag, "User canceled name entry")
c.resume(null)
}
}
}).show(supportFragmentManager, mClassTag)
}
Use case is simply for this example is getting the name, then saving to the Database. It looks like this:
private fun saveAsDuplicateConfiguration(){
launch(UI){
setIsActionInProgress(true)
val configName = withContext(DefaultDispatcher) { getNameFromDialog() }
if(configName == null){
showFancyToast(getString(R.string.canceled), true, FancyToast.INFO)
}else{
withContext(DefaultDispatcher){
try{
withTimeout(TIMEOUT_FOR_DB_INTERACTION_MS){
A35Log.v(mClassTag, "inserting copy of config with name :$configName")
val configCopy = DeviceAndConfigurationHelper.getCopyOfConfigurationModel(mSelectedConfiguration!!)
configCopy.setConfigName(configName)
configCopy.setDeviceType(FeatureHelper.PRO_DEVICE_KEY) //todo should come from BLE eventually
if(SSDBHelper.insertConfiguration(configCopy) > 0){
showFancyToast(getString(R.string.successfully_saved), true, FancyToast.SUCCESS)
finishCurrentActivity(SSGlobals.TimeOuts.TOAST_DISPLAY_DELAY_CLOSE_MS, true)
}else{
showFancyToast(getString(R.string.error_saving_copy))
}
}
}catch (e: TimeoutCancellationException) {
showFancyToast(getString(R.string.error_timed_out) + ", " + getString(R.string.error_please_try_again_or_press_back), true, FancyToast.ERROR, "TimedOut with error: ${e.message}")
}catch(ex: JobCancellationException){
showFancyToast(getString(R.string.canceled))
}catch(ex: Exception){
showFancyToast(getString(R.string.error_saving_copy) + ", " + getString(R.string.error_please_try_again_or_press_back), true, FancyToast.ERROR, "Error deleting: ${ex.message}")
}finally {
setIsActionInProgress(false)
}
}
}
}
}
As you can see the GetNameFromDialog is a blocking and waiting method so the saveAsDuplicateConfiguration doesn't move on until it is completed with getting the name, then it attempts to use it.
So moral of the story is that Coroutines are amazing and allow for clean async code, but the learning curve is steep. So do what you are comfortable with, but "and I can't stress this enough". Do not attempt to do dialogs and UI retrievals from inside an AsyncTask that will be asking for memory leaks, life cycle issues, and poor code management.
You could really make it bad by trying to pass in a listener and put the processing into the dialog callback of your session authenticator inside your asynctask, but that could fail miserably because garbage collection can take that object when it exits the execute method, so that would be a VERY bad idea.
Hope that helps, ask questions if you have any.
Happy Coding.
Answered By - Sam
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.