Is there an solution for java.lang.IllegalStateException: Reply already submitted - dart

I want to use pusher sdk in Flutter from android native code because its library no yet completely supported in flutter but when i send first message it received it successfully the next message make app crush with Reply already submitted error her on this line result.success(txt);
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "demo.gawkat.com/info";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler((methodCall, result) -> {
final Map<String, Object> arguments = methodCall.arguments();
if (methodCall.method.equals("getMessage")) {
Pusher pusher = new Pusher("faa685e4bb3003eb825c");
pusher.connect();
Channel channel = pusher.subscribe("messages");
channel.bind("new_message", (channelName, eventName, data) -> runOnUiThread(() -> {
Gson gson = new Gson();
Message message = gson.fromJson(data, Message.class);
String txt = message.text;
result.success(txt);
}));
}
});
}
}
Flutter code:
Future<String> _getMessage() async {
String value;
try {
value = await platform.invokeMethod('getMessage');
} catch (e) {
print(e);
}
return value;
}
Error is
FATAL EXCEPTION: main
Process: com.example.flutter_app, PID: 6296
java.lang.IllegalStateException: Reply already submitted
at io.flutter.view.FlutterNativeView$PlatformMessageHandlerImpl$1.reply(FlutterNativeView.java:197)
at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler$1.success(MethodChannel.java:204)
at com.example.flutter_app.MainActivity.lambda$null$0(MainActivity.java:40)
at com.example.flutter_app.-$$Lambda$MainActivity$axbDTe2B0rhavWD22s4E8-fuCaQ.run(Unknown Source:4)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767

I think it is happening after Flutter upgrade > 1.5.4.hotfix.
Anyway, Yes there is a solution (Refer this github issue),
In your Activitybelow onCreate() add this class:
private static class MethodResultWrapper implements MethodChannel.Result {
private MethodChannel.Result methodResult;
private Handler handler;
MethodResultWrapper(MethodChannel.Result result) {
methodResult = result;
handler = new Handler(Looper.getMainLooper());
}
#Override
public void success(final Object result) {
handler.post(
new Runnable() {
#Override
public void run() {
methodResult.success(result);
}
});
}
#Override
public void error(
final String errorCode, final String errorMessage, final Object errorDetails) {
handler.post(
new Runnable() {
#Override
public void run() {
methodResult.error(errorCode, errorMessage, errorDetails);
}
});
}
#Override
public void notImplemented() {
handler.post(
new Runnable() {
#Override
public void run() {
methodResult.notImplemented();
}
});
}
}
Then, instead of using MethodChannel result to setMethodCallHandler argument callback add name as rawResult and then inside that callback, add this line:
MethodChannel.Result result = new MethodResultWrapper(rawResult);
As below:
//......
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
(call, rawResult) -> {
MethodChannel.Result result = new MethodResultWrapper(rawResult);
//.....

I use flags for this problem.
Just make sure that methods of same channels are called simultaneously.
The problem seem to appear then.
If two methods needs to be called simulatenously without any problem define both methods in 2 different channels
var resultMap = Map<String, MethodChannel.Result> = HashMap()
new MethodChannel(getFlutterView(), CHANNEL_1).setMethodCallHandler((methodCall, result) -> {
final Map<String, Object> arguments = methodCall.arguments();
if (methodCall.method.equals("method1")) {
// implement method 1
}
});
new MethodChannel(getFlutterView(), CHANNEL_2).setMethodCallHandler((methodCall, result) -> {
final Map<String, Object> arguments = methodCall.arguments();
if (methodCall.method.equals("method2")) {
resultMap = resultMap + mapOf(CHANNEL_2 to MethodResultWrapper(result) // use this later to return result
// implement method2
result.success(true) // or whatever value
}
});
This reduce the chance of "Reply already submitted" error.
Incase if you are using MethodResultWrapper as #Blasanka answer use flags before result.success
when method is invoked set flag to true
val methodCheckFlag: Boolean = true
then when result need to be returned
if(methodCheckFlag) {
methodCheckFlag = false;
methodWrapperResult?.success(true) // or what ever value to return
}
or use the saved MethodResultWrapper as
if(methodCheckFlag) {
methodCheckFlag = false;
resultMap[CHANNEL_2]?.success(true) // or what ever value to return
}

Related

Android Volley POST request out of synchronization

I'm using the implementation 'com.android.volley:volley:1.1.1' to make a POST call to a REST web service. Unfortunately, the conclusion (i.e. User does not exist) is reached before the response comes back from the server and is always wrong (In reality user actually does exist).
On the console, the logger messages appear in the wrong order:
E/: THIS IS SUPPOSED TO HAPPEN SECOND - USER NOT FOUND ALERT
I/: THIS IS SUPPOSED TO HAPPEN FIRST: VALIDATING DATA
After hours of reading, I found that a Callback Interface will ensure proper execution order. However, after implementing it, the result is the same. What could be wrong, please?
ControladorLoginExistente.Java
public class ControladorLoginUsrExistente {
public AbstractMap.SimpleEntry<String, Map<String, String>> callEndpointLoginUsrExistente(Context context) {
try {
JSONObject jsonRequest = new JSONObject();
jsonRequest.put("email", "mymail#themail.com");
jsonRequest.put("password", "12345");
final JSONObject[] jsonResponse = {null};
new PostRequestConVolley().getResponse(Constantes.URL_ACCESO_USUARIO_EXISTENTE, jsonRequest, context, new VolleyCallback() {
#Override
public void onSuccessResponse(JSONObject jsonObject) {
jsonResponse[0] = jsonObject;
Log.i(null,"THIS IS SUPPOSED TO HAPPEN FIRST: VALIDATING DATA");
}
});
Boolean exito = jsonResponse[0].getBoolean("exito");
String descripcion = jsonResponse[0].getString("descripcion");
String codigoHttp = jsonResponse[0].getString("codigoHttp");
JSONArray respuestaTransaccion = jsonResponse[0].getJSONArray("respuestaTransaccion");
if(exito == false || codigoHttp.equals("200")){
Log.e(null,"THIS IS SUPPOSED TO HAPPEN SECOND: USER NOT FOUND ALERT");
return new AbstractMap.SimpleEntry<>(descripcion, new HashMap<>());
}
Log.i(null,"THIS IS SUPPOSED TO HAPPEN SECOND: USER NOT FOUND ALERT");
return new AbstractMap.SimpleEntry<>(Constantes.EXITO, new HashMap<>());
} catch (Exception ex) {
Log.e(null,"THIS IS SUPPOSED TO HAPPEN SECOND: USER NOT FOUND ALERT");
return new AbstractMap.SimpleEntry<>("ERROR: " + ex.toString(), new HashMap<>());
}
}
}
PostRequestConVolley.java
public class PostRequestConVolley {
public JSONObject getResponse(String url, JSONObject body, Context context, final VolleyCallback callback) {
try {
RequestQueue queue = Volley.newRequestQueue(context);
JsonObjectRequest jsonRequest = new JsonObjectRequest(POST, url, body,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
callback.onSuccessResponse(response);
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e(null, error.toString());
}
}) {
#Override
public Map<String, String> getHeaders() {
Map<String, String> params = new HashMap<String, String>();
params.put("Content-Type", "application/json");
params.put("Connection", "keep-alive");
return params;
}
#Override
public String getBodyContentType() {
return "application/json; charset=utf-8";
}
#Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
Log.i(null, "El HTTP code es:" + response.statusCode);
return super.parseNetworkResponse(response);
}
};
queue.add(jsonRequest);
} catch (Exception ex) {
ex.printStackTrace();
}
return body;
}
}
VolleyCallbackInterface
import org.json.JSONObject;
public interface VolleyCallback {
void onSuccessResponse(JSONObject jsonObject);
}

Why isn't an custom implemented VaadinServiceInitListener is listening in vaadin 13.0.2?

I would like to validate user is signed in or not to achieve it i found something called VaadinServiceInitListener in vaadin 13.0.2 This class is used to listen to BeforeEnter event of all UIs in order to check whether a user is signed in or not before allowing entering any page.
I have created an vaadin 13.0.2 project with app-layout-addon by appreciated implemented login functionality and VaadinServiceInitListener to check whether a user is signed in or not.
public class AAACATInitListener implements VaadinServiceInitListener {
private static final long serialVersionUID = 1L;
private static InAppSessionContextImpl appContextImpl;
#Override
public void serviceInit(ServiceInitEvent event) {
System.out.println("in service init event");
event.getSource().addUIInitListener(new UIInitListener() {
private static final long serialVersionUID = 1L;
#Override
public void uiInit(UIInitEvent event) {
event.getUI().addBeforeEnterListener(new BeforeEnterListener() {
private static final long serialVersionUID = 1L;
#Override
public void beforeEnter(BeforeEnterEvent event) {
appContextImpl = (InAppSessionContextImpl)VaadinSession.getCurrent().getAttribute("context");
if (appContextImpl == null) {
WebBrowser webBrowser = UI.getCurrent().getSession().getBrowser();
String address = webBrowser.getAddress();
if(RememberAuthService.isAuthenticated(address) != null && !RememberAuthService.isAuthenticated(address).isEmpty()) {
//System.out.println("Found Remembered User....");
IBLSessionContext iblSessionContext = null;
try {
iblSessionContext = new UserBLManager().doRememberedStaffUserLogin(RememberAuthService.isAuthenticated(address), "");
if(iblSessionContext != null) {
InAppSessionContextImpl localAppContextImpl = new InAppSessionContextImpl();
localAppContextImpl.setBLSessionContext(iblSessionContext);
localAppContextImpl.setModuleGroupList(iblSessionContext.getSessionAccessControl().getPermittedModuleGroups());
appContextImpl = localAppContextImpl;
event.rerouteTo(ApplicationMainView.class);
}else {
Notification.show("Your access has been expired, Please contact your administrator", 5000, Position.BOTTOM_CENTER);
}
} catch (AuthenticationFailedException e) {
Notification.show("Authentication Failed, Please Reset Cookies And Try Again", 5000, Position.BOTTOM_CENTER);
} catch (Exception e){
e.printStackTrace();
Notification.show("Unexpected Error Occurred, Please Reset Cookies And Try Again", 5000, Position.BOTTOM_CENTER);
}
}else {
System.out.println("Session context is null, creating new context");
appContextImpl = new InAppSessionContextImpl();
VaadinSession.getCurrent().setAttribute("context", appContextImpl);
event.rerouteTo(LoginView.class);
}
} else {
System.out.println("Session context is not null");
InAppSessionContextImpl localAppContextImpl = new InAppSessionContextImpl();
localAppContextImpl.setBLSessionContext(appContextImpl.getBLSessionContext());
localAppContextImpl.setModuleGroupList(appContextImpl.getModuleGroupList());
appContextImpl = localAppContextImpl;
event.rerouteTo(ApplicationMainView.class);
}
}
});
}
});
}
public static void setBLSessionContext(IBLSessionContext iblSessionContext) {
appContextImpl.setBLSessionContext(iblSessionContext);
}
public static void setModuleGroupList(List<ModuleGroupVO> moduleGroupList) {
appContextImpl.setModuleGroupList(moduleGroupList);
}
private class InAppSessionContextImpl implements InAppSessionContext {
private static final long serialVersionUID = 1L;
private List<ModuleGroupVO> moduleGroupList;
private IBLSessionContext iblSessionContext;
private Map<String, Object> attributeMap;
public InAppSessionContextImpl() {
this.attributeMap = new HashMap<String, Object>();
}
#Override
public List<ModuleGroupVO> getModuleGroupList() {
return moduleGroupList;
}
public void setModuleGroupList(List<ModuleGroupVO> moduleGroupList) {
this.moduleGroupList = moduleGroupList;
}
#Override
public IBLSessionContext getBLSessionContext() {
return iblSessionContext;
}
public void setBLSessionContext(IBLSessionContext iblSessionContext) {
this.iblSessionContext = iblSessionContext;
}
#Override
public IBLSession getBLSession() {
if(iblSessionContext != null)
return iblSessionContext.getBLSession();
return null;
}
#Override
public boolean isPermittedAction(String actionAlias) {
if (getBLSessionContext() != null) {
if (getBLSessionContext().getSessionAccessControl() != null) {
return getBLSessionContext().getSessionAccessControl().isPermittedAction(actionAlias);
}
}
return false;
}
#Override
public void setAttribute(String key, Object attribute) {
attributeMap.put(key, attribute);
}
#Override
public Object getAttribute(String key) {
return attributeMap.get(key);
}
}
}
Expected results redirect to login page if user not signed in or else to main application page but AAACATInitListener is not listening.
If you are using Spring, simply add a #Component annotation to the class and it should work. If youre not using Spring, follow #codinghaus' answer.
To make Vaadin recognize the VaadinServiceInitListener you have to create a file called com.vaadin.flow.server.VaadinServiceInitListener and put it under src/main/resources/META-INF/services. Its content should be the full path to the class that implements the VaadinServiceInitListener interface. Did you do that?
You can also find a description on that in the tutorial.
The correct pattern to use beforeEnter(..) is not do it via VaadinServiceInitListener , instead you should implement BeforeEnterObserver interface in the view where you need use it and override beforeEnter(..) method with your implementation.
public class MainView extends VerticalLayout implements RouterLayout, BeforeEnterObserver {
...
#Override
public void beforeEnter(BeforeEnterEvent event) {
...
}
}

FeignClient throws instead of returning ResponseEntity with error http status

As I'm using ResponseEntity<T> as return value for my FeignClient method, I was expecting it to return a ResponseEntity with 400 status if it's what the server returns. But instead it throws a FeignException.
How can I get a proper ResponseEntity instead of an Exception from FeignClient ?
Here is my FeignClient:
#FeignClient(value = "uaa", configuration = OauthFeignClient.Conf.class)
public interface OauthFeignClient {
#RequestMapping(
value = "/oauth/token",
method = RequestMethod.POST,
consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
ResponseEntity<OauthTokenResponse> token(Map<String, ?> formParams);
class Conf {
#Value("${oauth.client.password}")
String oauthClientPassword;
#Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
#Bean
public Contract feignContract() {
return new SpringMvcContract();
}
#Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("web-client", oauthClientPassword);
}
}
}
and here how I use it:
#PostMapping("/login")
public ResponseEntity<LoginTokenPair> getTokens(#RequestBody #Valid LoginRequest userCredentials) {
Map<String, String> formData = new HashMap<>();
ResponseEntity<OauthTokenResponse> response = oauthFeignClient.token(formData);
//code never reached if contacted service returns a 400
...
}
By the way, solution I gave before works, but my initial intention is bad idea: an error is an error and should not be handled on nominal flow. Throwing an exception, like Feign does, and handling it with an #ExceptionHandler is a better way to go in Spring MVC world.
So two solutions:
add an #ExceptionHandler for FeignException
configure the FeignClient with an ErrorDecoder to translate the error in an Exception your business layer knows about (and already provide #ExceptionHandler for)
I prefer second solution because received error message structure is likely to change from a client to an other, so you can extract finer grained data from those error with a per-client error decoding.
FeignClient with conf (sorry for the noise introduced by feign-form)
#FeignClient(value = "uaa", configuration = OauthFeignClient.Config.class)
public interface OauthFeignClient {
#RequestMapping(
value = "/oauth/token",
method = RequestMethod.POST,
consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
DefaultOAuth2AccessToken token(Map<String, ?> formParams);
#Configuration
class Config {
#Value("${oauth.client.password}")
String oauthClientPassword;
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
#Bean
public Decoder springDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(messageConverters));
}
#Bean
public Contract feignContract() {
return new SpringMvcContract();
}
#Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("web-client", oauthClientPassword);
}
#Bean
public ErrorDecoder uaaErrorDecoder(Decoder decoder) {
return (methodKey, response) -> {
try {
OAuth2Exception uaaException = (OAuth2Exception) decoder.decode(response, OAuth2Exception.class);
return new SroException(
uaaException.getHttpErrorCode(),
uaaException.getOAuth2ErrorCode(),
Arrays.asList(uaaException.getSummary()));
} catch (Exception e) {
return new SroException(
response.status(),
"Authorization server responded with " + response.status() + " but failed to parse error payload",
Arrays.asList(e.getMessage()));
}
};
}
}
}
Common business exception
public class SroException extends RuntimeException implements Serializable {
public final int status;
public final List<String> errors;
public SroException(final int status, final String message, final Collection<String> errors) {
super(message);
this.status = status;
this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SroException)) return false;
SroException sroException = (SroException) o;
return status == sroException.status &&
Objects.equals(super.getMessage(), sroException.getMessage()) &&
Objects.equals(errors, sroException.errors);
}
#Override
public int hashCode() {
return Objects.hash(status, super.getMessage(), errors);
}
}
Error handler (extracted from a ResponseEntityExceptionHandler extension)
#ExceptionHandler({SroException.class})
public ResponseEntity<Object> handleSroException(SroException ex) {
return new SroError(ex).toResponse();
}
Error response DTO
#XmlRootElement
public class SroError implements Serializable {
public final int status;
public final String message;
public final List<String> errors;
public SroError(final int status, final String message, final Collection<String> errors) {
this.status = status;
this.message = message;
this.errors = Collections.unmodifiableList(new ArrayList<>(errors));
}
public SroError(final SroException e) {
this.status = e.status;
this.message = e.getMessage();
this.errors = Collections.unmodifiableList(e.errors);
}
protected SroError() {
this.status = -1;
this.message = null;
this.errors = null;
}
public ResponseEntity<Object> toResponse() {
return new ResponseEntity(this, HttpStatus.valueOf(this.status));
}
public ResponseEntity<Object> toResponse(HttpHeaders headers) {
return new ResponseEntity(this, headers, HttpStatus.valueOf(this.status));
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SroError)) return false;
SroError sroException = (SroError) o;
return status == sroException.status &&
Objects.equals(message, sroException.message) &&
Objects.equals(errors, sroException.errors);
}
#Override
public int hashCode() {
return Objects.hash(status, message, errors);
}
}
Feign client usage notice how errors are transparently handled (no try / catch) thanks to #ControllerAdvice & #ExceptionHandler({SroException.class})
#RestController
#RequestMapping("/uaa")
public class AuthenticationController {
private static final BearerToken REVOCATION_TOKEN = new BearerToken("", 0L);
private final OauthFeignClient oauthFeignClient;
private final int refreshTokenValidity;
#Autowired
public AuthenticationController(
OauthFeignClient oauthFeignClient,
#Value("${oauth.ttl.refresh-token}") int refreshTokenValidity) {
this.oauthFeignClient = oauthFeignClient;
this.refreshTokenValidity = refreshTokenValidity;
}
#PostMapping("/login")
public ResponseEntity<LoginTokenPair> getTokens(#RequestBody #Valid LoginRequest userCredentials) {
Map<String, String> formData = new HashMap<>();
formData.put("grant_type", "password");
formData.put("client_id", "web-client");
formData.put("username", userCredentials.username);
formData.put("password", userCredentials.password);
formData.put("scope", "openid");
DefaultOAuth2AccessToken response = oauthFeignClient.token(formData);
return ResponseEntity.ok(new LoginTokenPair(
new BearerToken(response.getValue(), response.getExpiresIn()),
new BearerToken(response.getRefreshToken().getValue(), refreshTokenValidity)));
}
#PostMapping("/logout")
public ResponseEntity<LoginTokenPair> revokeTokens() {
return ResponseEntity
.ok(new LoginTokenPair(REVOCATION_TOKEN, REVOCATION_TOKEN));
}
#PostMapping("/refresh")
public ResponseEntity<BearerToken> refreshToken(#RequestHeader("refresh_token") String refresh_token) {
Map<String, String> formData = new HashMap<>();
formData.put("grant_type", "refresh_token");
formData.put("client_id", "web-client");
formData.put("refresh_token", refresh_token);
formData.put("scope", "openid");
DefaultOAuth2AccessToken response = oauthFeignClient.token(formData);
return ResponseEntity.ok(new BearerToken(response.getValue(), response.getExpiresIn()));
}
}
So, looking at source code, it seams that only solution is actually using feign.Response as return type for FeignClient methods and hand decoding the body with something like new ObjectMapper().readValue(response.body().asReader(), clazz) (with a guard on 2xx status of course because for error statuses, it's very likely that body is an error description and not a valid payload ;).
This makes possible to extract and forward status, header, body, etc. even if status is not in 2xx range.
Edit:
Here is a way to forward status, headers and mapped JSON body (if possible):
public static class JsonFeignResponseHelper {
private final ObjectMapper json = new ObjectMapper();
public <T> Optional<T> decode(Response response, Class<T> clazz) {
if(response.status() >= 200 && response.status() < 300) {
try {
return Optional.of(json.readValue(response.body().asReader(), clazz));
} catch(IOException e) {
return Optional.empty();
}
} else {
return Optional.empty();
}
}
public <T, U> ResponseEntity<U> toResponseEntity(Response response, Class<T> clazz, Function<? super T, ? extends U> mapper) {
Optional<U> payload = decode(response, clazz).map(mapper);
return new ResponseEntity(
payload.orElse(null),//didn't find a way to feed body with original content if payload is empty
convertHeaders(response.headers()),
HttpStatus.valueOf(response.status()));
}
public MultiValueMap<String, String> convertHeaders(Map<String, Collection<String>> responseHeaders) {
MultiValueMap<String, String> responseEntityHeaders = new LinkedMultiValueMap<>();
responseHeaders.entrySet().stream().forEach(e ->
responseEntityHeaders.put(e.getKey(), new ArrayList<>(e.getValue())));
return responseEntityHeaders;
}
}
that can be used as follow:
#PostMapping("/login")
public ResponseEntity<LoginTokenPair> getTokens(#RequestBody #Valid LoginRequest userCredentials) throws IOException {
Response response = oauthFeignClient.token();
return feignHelper.toResponseEntity(
response,
OauthTokenResponse.class,
oauthTokenResponse -> new LoginTokenPair(
new BearerToken(oauthTokenResponse.access_token, oauthTokenResponse.expires_in),
new BearerToken(oauthTokenResponse.refresh_token, refreshTokenValidity)));
}
This saves headers and status code, but error message is lost :/

Unable to update row of tableView after connecting to a url

I am building a download manager
Here I have shown a test code which tries to update fileNameColumn of a row of tableView but it is not being updated after I connect to url
To be specific, here fileName remains hello1 and it doesnt get updated to hello2. Yhy's that so?
Main.java :
public static TableView<DownloadEntry> downloadsTable;
public TableColumn<DownloadEntry, String> fileNameColumn;
public void initialize(URL location, ResourceBundle resources) {
downloadsTable = new TableView<DownloadEntry>();
fileNameColumn = new TableColumn<>("File Name");
fileNameColumn.setCellValueFactory(new PropertyValueFactory<>("fileName"));
executor = Executors.newFixedThreadPool(4);
}
public void addDownloadButtonClicked() {
try{
String urlText = urlTextBox.getText();
DownloadEntry task = new DownloadEntry(new URL(urlText));
downloadsTable.getItems().add(task);
executor.execute(task);
}
catch(Exception e) {
System.out.println("addDownloadButtonClicked: " + e);
}
}
DownloadEntry.java:
public class DownloadEntry extends Task<Void> {
public SimpleStringProperty fileName;
public URL url;
//Constructor
public DownloadEntry(URL ur) throws Exception{
fileName = new SimpleStringProperty("hello");
url = ur;
}
#Override
protected Void call() {
try {
HttpURLConnection connect=(HttpURLConnection)url.openConnection();
fileName.set("hello1");
connect.connect();
fileName.set("hello2");
}
catch(Exception E) {
this.updateMessage("Error");
E.printStackTrace();
}
return null;
}
public String getFileName() {
return fileName.get();
}
public void setFileName(String fileName) {
this.fileName = new SimpleStringProperty(fileName);
}
}
Please tell if you need more details..
Your model is incorrectly implemented. The setFileName method should be
public void setFileName(String fileName) {
this.fileName.set(fileName);
}
(The problem with your implementation is that the table is still observing the old property, not the new one you create.)
You will also need to provide a "property accessor" method:
public StringProperty fileNameProperty() {
return fileName ;
}
which will allow the table to properly bind to the property (so that it "knows" when its value changes).

Can only parse TITLE tag -- LINK, DESCRIPTION, PUBDATE and others will not parse. (RSS / SAX)

I'm new to android development and I have been struggling to parse more than one tag at a time and display it in a ListView.
I'm using SAX parser, here is my RssParseHandler code.
public class RssParseHandler extends DefaultHandler {
private List<RssItem> rssItems;
private RssItem currentMessage;
//private StringBuilder builder;
private boolean parseLink;
private boolean parseTitle;
private boolean parseDate;
private boolean parseDes;
public RssParseHandler() {
rssItems = new ArrayList();
}
public List<RssItem> getItems() {
return this.rssItems;
}
#Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, name, attributes);
if (localName.equalsIgnoreCase("item")) {
this.currentMessage = new RssItem();
} else if (localName.equalsIgnoreCase("title")) {
//currentMessage.setTitle(builder.toString());
parseTitle = true;
} else if (localName.equalsIgnoreCase("link")) {
//currentMessage.setLink(builder.toString());
parseLink = true;
} else if (localName.equalsIgnoreCase("description")) {
//currentMessage.setDescription(builder.toString());
parseDes = true;
} else if (localName.equalsIgnoreCase("pubDate")) {
//currentMessage.setDate(builder.toString());
parseDate = true;
}
//parsing enclosure tag
else if ("enclosure".equals(localName)) {
// Get tags attributes number
int attrsLength = attributes.getLength();
for (int i = 0; i < attrsLength; i++) {
String attrName = attributes.getQName(i); // attribute name
if ("url".equals(attrName)) // This tag has only one attribute but it is better to check it name is correct
currentMessage.getLink();
}
}
}
#Override
public void endElement(String uri, String localName, String name)
throws SAXException {
super.endElement(uri, localName, name);
if (this.currentMessage != null) {
if (localName.equalsIgnoreCase("item")) {
rssItems.add(currentMessage);
//currentMessage = null;
} else if (localName.equalsIgnoreCase("link")) {
//currentMessage.setLink(builder.toString());
//parseLink = false;
} else if (localName.equalsIgnoreCase("description")) {
//currentMessage.setDescription(builder.toString());
//parseDes = false;
} else if (localName.equalsIgnoreCase("pubDate")){
//currentMessage.setDate(builder.toString());
parseDate = false;
} else if (localName.equalsIgnoreCase("title")) {
//currentMessage.setTitle(builder.toString());
parseTitle = false;
}
//builder.setLength(0);
}
}
#Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
//builder.append(ch, start, length);
if (parseTitle) {
if (currentMessage != null)
currentMessage.setTitle(new String(ch, start, length));
} else if (parseLink) {
if (currentMessage != null) {
currentMessage.setLink(new String(ch, start, length));
//parseLink = false;
}
} else if (parseDes) {
if (currentMessage != null)
currentMessage.setDescription(new String(ch, start, length));
//parseLink = false;
} else if (parseDate) {
if (currentMessage != null) {
currentMessage.setDate(new String(ch, start, length));
//currentMessage.setDate(new String(ch, start, length));
//parseDesc = false;
}
}
}
}
Here is the code for the Listview:
public class ReaderAppActivity extends Fragment {
private ReaderAppActivity local;
private ListView mList;
/**
* This method creates main application view
*/
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//super.onCreate(savedInstanceState);
// Set view
//setContentView(R.layout.fragment_rss);
local = this;
//int position = getArguments().getInt("position");
// String url = getArguments().getString("url");
// List of rivers
String[] menus = getResources().getStringArray(R.array.menus);
// Creating view corresponding to the fragment
View v = inflater.inflate(R.layout.fragment_rss, container, false);
// Set reference to this activity
//local = this;
GetRSSDataTask task = new GetRSSDataTask();
// Start download RSS task
task.execute("http://thechurchofwhatshappeningnow.libsyn.com/rss");
//task.execute(url);
// Debug the thread name
Log.d("ITCRssReader", Thread.currentThread().getName());
//mList = (ListView) findViewById(R.id.rssListMainView);
return v;
}
private class GetRSSDataTask extends AsyncTask<String, Void, List<RssItem> > {
#Override
protected List<RssItem> doInBackground(String... urls) {
// Debug the task thread name
Log.d("ITCRssReader", Thread.currentThread().getName());
try {
// Create RSS reader
RssReader rssReader = new RssReader(urls[0]);
// Parse RSS, get items
return rssReader.getItems();
} catch (Exception e) {
Log.e("ITCRssReader", e.getMessage());
}
return null;
}
#Override
protected void onPostExecute(List<RssItem> result) {
// Get a ListView from main view
ListView mList = (ListView) getView().findViewById(R.id.rssListMainView);
// Create a list adapter
ArrayAdapter<RssItem> adapter = new ArrayAdapter<RssItem>(getActivity(),R.layout.rss_text, result);
//ArrayAdapter<RssItem> adapter = new ArrayAdapter<RssItem>(getActivity(),R.layout.fragment_rss, result);
// Set list adapter for the ListView
mList.setAdapter(adapter);
// Set list view item click listener
mList.setOnItemClickListener(new ListListener(result, getActivity()));
}
}
}
What am I doing wrong? I can't figure it out. I would like to parse, the link, description, pubDate, and pass them into the ListView. Ideally I would only display the title and episode number in the listview, and pass the other tags into String, so I can display them when I click an item in the listView.
I've created another class called SingleMenuItem to be called when I click an item in the ListView, it's just filler code right now, it does not display anything because the items aren't parsed.
Any help would be appreciated. Here is a RSS link to the feed:
public class SingleMenuItem extends Activity {
// XML node keys
static final String KEY_NAME = "name";
static final String KEY_DATE = "pubdate";
static final String KEY_DESC = "description";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.single_list_item);
// getting intent data
Intent in = getIntent();
// Get XML values from previous intent
String name = in.getStringExtra(KEY_NAME);
String date = in.getStringExtra(KEY_DATE);
String description = in.getStringExtra(KEY_DESC);
// Displaying all values on the screen
TextView lblName = (TextView) findViewById(R.id.name_label);
TextView lblDate = (TextView) findViewById(R.id.date_label);
TextView lblDesc = (TextView) findViewById(R.id.description_label);
lblName.setText(name);
lblDate.setText(date);
lblDesc.setText(description);
}
}
Here is the code for my ReaderAppActivty that puts the results of the parsing into the ListView:
public class ReaderAppActivity extends Fragment {
private ReaderAppActivity local;
private ListView mList;
/**
* This method creates main application view
*/
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//super.onCreate(savedInstanceState);
// Set view
//setContentView(R.layout.fragment_rss);
local = this;
//int position = getArguments().getInt("position");
// String url = getArguments().getString("url");
// List of rivers
String[] menus = getResources().getStringArray(R.array.menus);
// Creating view corresponding to the fragment
View v = inflater.inflate(R.layout.fragment_rss, container, false);
// Set reference to this activity
//local = this;
GetRSSDataTask task = new GetRSSDataTask();
// Start download RSS task
task.execute("http://thechurchofwhatshappeningnow.libsyn.com/rss");
//task.execute(url);
// Debug the thread name
Log.d("ITCRssReader", Thread.currentThread().getName());
//mList = (ListView) findViewById(R.id.rssListMainView);
return v;
}
private class GetRSSDataTask extends AsyncTask<String, Void, List<RssItem> > {
#Override
protected List<RssItem> doInBackground(String... urls) {
// Debug the task thread name
Log.d("ITCRssReader", Thread.currentThread().getName());
try {
// Create RSS reader
RssReader rssReader = new RssReader(urls[0]);
// Parse RSS, get items
return rssReader.getItems();
} catch (Exception e) {
Log.e("ITCRssReader", e.getMessage());
}
return null;
}
#Override
protected void onPostExecute(List<RssItem> result) {
// Get a ListView from main view
ListView mList = (ListView) getView().findViewById(R.id.rssListMainView);
// Create a list adapter
ArrayAdapter<RssItem> adapter = new ArrayAdapter<RssItem>(getActivity(),R.layout.rss_text, result);
//ArrayAdapter<RssItem> adapter = new ArrayAdapter<RssItem>(getActivity(),R.layout.fragment_rss, result);
// Set list adapter for the ListView
mList.setAdapter(adapter);
// Set list view item click listener
mList.setOnItemClickListener(new ListListener(result, getActivity()));
}
}
}
Based on the amount of commented-out code in your RssParseHandler, you've clearly been struggling with this for a bit, and some early attempts were closer to right than what you've got now.
The issue with your current code appears to be that you're not consistently resetting the booleans that drive which part of the item you're setting. Debugging through it, I saw it setting a date into the link field at some point.
But you're actually doing some of that setting in the wrong method, as the characters method doesn't necessarily give you the full contents of the tag. You need to use a Stringbuilder, and I can see from commented-out code that you tried that at some point.
If you collect the text in a stringbuilder and do all the setting in the endElement method, you don't really need the booleans at all, as the endElement method has knowledge of which tag you're ending.
Here's a working version that's perhaps not too far from something you had at some point but which gets rid of all those flag fields.
public class RssParseHandler extends DefaultHandler {
private List<RssItem> rssItems;
private RssItem currentMessage;
private StringBuilder builder;
public RssParseHandler() {
rssItems = new ArrayList<>();
builder = new StringBuilder();
}
public List<RssItem> getItems() {
return this.rssItems;
}
#Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, name, attributes);
builder.setLength(0);
if (localName.equalsIgnoreCase("item")) {
this.currentMessage = new RssItem();
}
}
#Override
public void endElement(String uri, String localName, String name)
throws SAXException {
super.endElement(uri, localName, name);
if (this.currentMessage != null) {
if (localName.equalsIgnoreCase("item")) {
rssItems.add(currentMessage);
currentMessage = null;
} else if (localName.equalsIgnoreCase("link")) {
currentMessage.setLink(builder.toString());
} else if (localName.equalsIgnoreCase("description")) {
currentMessage.setDescription(builder.toString());
} else if (localName.equalsIgnoreCase("pubDate")){
currentMessage.setDate(builder.toString());
} else if (localName.equalsIgnoreCase("title")) {
currentMessage.setTitle(builder.toString());
}
}
}
#Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
builder.append(ch, start, length);
}
}

Resources