I'm using javafx's webengine to display a web page. And on the page, there's script calling window.confirm. I already know how to set the confirm handler and how to show a modal-like dialog.
My question is how can I get the user's choice before handler returns?
webEngine.setConfirmHandler(new Callback<String, Boolean>() {
#Override
public Boolean call(String message) {
// Show the dialog
...
return true; // How can I get user's choice here?
}
});
As described in javafx-jira.kenai.com/browse/RT-19783, we can use the new method showAndWait which is available in JavaFx 2.2 to achieve this.
Stage class:
/**
* Show the stage and wait for it to be closed before returning to the
* caller. This must be called on the FX Application thread. The stage
* must not already be visible prior to calling this method. This must not
* be called on the primary stage.
*
* #throws IllegalStateException if this method is called on a thread
* other than the JavaFX Application Thread.
* #throws IllegalStateException if this method is called on the
* primary stage.
* #throws IllegalStateException if this stage is already showing.
*/
public void showAndWait();
#jewelsea created a sample on https://gist.github.com/2992072. Thanks!
Related
I am using Spring-Kafka to consume messages from Confluent Kafka and I am using RetryTopicConfiguration Bean to configure the topics and backoff strategy. My application works fine but I see a lot of WARNING log like the one below in my logs and I am wondering if my configuration is incorrect.
DeadLetterPublishingRecovererFactory$1 : Destination resolver returned non-existent partition flow-events-retry-0-4, KafkaProducer will determine partition to use for this topic
Config Code
#Bean
public KafkaTemplate kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
#Bean
public RetryTopicConfiguration myRetryableTopic(KafkaTemplate<String, Object> template) {
return RetryTopicConfigurationBuilder
.newInstance()
.exponentialBackoff(BACKOFF_INITIAL_DELAY_10MINS, BACKOFF_EXPONENTIAL_MULTIPLIER_3, BACKOFF_MAX_DELAY_4HRS)
.maxAttempts(5)
.doNotAutoCreateRetryTopics()
.setTopicSuffixingStrategy(TopicSuffixingStrategy.SUFFIX_WITH_INDEX_VALUE)
.create(template);
}
The retry topics are created separately with 1 partition and replication factor of 3.
By default, the same partition as the original topic is used; you can override that behavior by overriding the DeadLetterPublishingRecovererFactory #Bean:
#Bean(RetryTopicInternalBeanNames.DEAD_LETTER_PUBLISHING_RECOVERER_FACTORY_BEAN_NAME)
DeadLetterPublishingRecovererFactory factory(DestinationTopicResolver resolver) {
DeadLetterPublishingRecovererFactory factory = new DeadLetterPublishingRecovererFactory(resolver) {
#Override
protected TopicPartition resolveTopicPartition(ConsumerRecord<?, ?> cr, DestinationTopic nextDestination) {
return new TopicPartition(nextDestination.getDestinationName(), -1); // Kafka Chooses
// return new TopicPartition(nextDestination.getDestinationName(), 0); // explict
}
};
factory.setDeadLetterPublishingRecovererCustomizer(dlpr -> {
// ...
});
return factory;
}
As you can see in this example, you can also customize DLPR properties here too.
/**
* Creates and returns the {#link TopicPartition}, where the original record should be forwarded.
* By default, it will use the partition same as original record's partition, in the next destination topic.
*
* <p>{#link DeadLetterPublishingRecoverer#checkPartition} has logic to check whether that partition exists,
* and if it doesn't it sets -1, to allow the Producer itself to assign a partition to the record.</p>
*
* <p>Subclasses can inherit from this method to override the implementation, if necessary.</p>
*
* #param cr The original {#link ConsumerRecord}, which is to be forwarded to DLT
* #param nextDestination The next {#link DestinationTopic}, where the consumerRecord is to be forwarded
* #return An instance of {#link TopicPartition}, specifying the topic and partition, where the cr is to be sent
*/
protected TopicPartition resolveTopicPartition(final ConsumerRecord<?, ?> cr, final DestinationTopic nextDestination) {
return new TopicPartition(nextDestination.getDestinationName(), cr.partition());
}
Using spring-cloud-stream from spring-cloud Hoxton.SR12 release with Kafka Binder.
Boot version: 2.5.2
Problem statement:
I would like to handle deserialisation errors by pushing them to a poison-pill topic with no retries.
Handle any other exceptions by retrying and then pushing to a parkingLot topic.
Do not retry ValidationException
This is my error handling code so far:
#Configuration
#Slf4j
public class ErrorHandlingConfig {
#Value("${errorHandling.parkingLotDestination}")
private String parkingLotDestination;
#Value("${errorHandling.retryAttempts}")
private long retryAttempts;
#Value("${errorHandling.retryIntervalMillis}")
private long retryIntervalMillis;
#Bean
public ListenerContainerCustomizer<AbstractMessageListenerContainer<byte[], byte[]>> customizer(SeekToCurrentErrorHandler errorHandler) {
return (container, dest, group) -> {
container.setErrorHandler(errorHandler);
};
}
#Bean
public SeekToCurrentErrorHandler errorHandler(DeadLetterPublishingRecoverer parkingLotPublisher) {
SeekToCurrentErrorHandler seekToCurrentErrorHandler = new SeekToCurrentErrorHandler(parkingLotPublisher, new FixedBackOff(retryIntervalMillis, retryAttempts));
seekToCurrentErrorHandler.addNotRetryableExceptions(ValidationException.class);
return seekToCurrentErrorHandler;
}
#Bean
public DeadLetterPublishingRecoverer parkingLotPublisher(KafkaOperations bytesTemplate) {
DeadLetterPublishingRecoverer deadLetterPublishingRecoverer = new DeadLetterPublishingRecoverer(bytesTemplate, (cr, e) -> new TopicPartition(parkingLotDestination, cr.partition()));
deadLetterPublishingRecoverer.setHeadersFunction((cr, e) -> cr.headers());
return deadLetterPublishingRecoverer;
}
}
I think what I have so far should cover the retryable exceptions being pushed to parking lot. How do I now add in the code to push failed deserialisation events to poison topic?
I want to do this outside of the binder/binding configuration and at the container level due to the outstanding issue of not being able to send to a custom dlqName.
I could use a ErrorHandlingDeserializer and call setFailedDeserializationFunction() on it that would contain a function that sends the message onto poison topic. Should I do this using a Source binding or raw KafkaOperations? I also need to work out how to hook this ErrorHandingDeserialiser into the ConsumerFactory.
Why are you using Hoxton with Boot 2.5? The proper cloud version for Boot 2.5.2 is 2020.0.3.
The SeekToCurrentErrorHandler already considers DeserializationExceptions to be fatal. See
/**
* Add exception types to the default list. By default, the following exceptions will
* not be retried:
* <ul>
* <li>{#link DeserializationException}</li>
* <li>{#link MessageConversionException}</li>
* <li>{#link ConversionException}</li>
* <li>{#link MethodArgumentResolutionException}</li>
* <li>{#link NoSuchMethodException}</li>
* <li>{#link ClassCastException}</li>
* </ul>
* All others will be retried.
* #param exceptionTypes the exception types.
* #since 2.6
* #see #removeNotRetryableException(Class)
* #see #setClassifications(Map, boolean)
*/
#SafeVarargs
#SuppressWarnings("varargs")
public final void addNotRetryableExceptions(Class<? extends Exception>... exceptionTypes) {
The ErrorHandlingDeserializer (without a function) adds the exception to a header; the DeadLetterPublishingRecoverer automatically extracts the original payload from the header and sets as the value() of the outgoing record (byte[]).
Since you are using native encoding, you will need two KafkaTemplates - one for the failed records that need to be re-serialized and one for the DeserializationExceptions (that uses a ByteArraySerializer.
See
/**
* Create an instance with the provided templates and destination resolving function,
* that receives the failed consumer record and the exception and returns a
* {#link TopicPartition}. If the partition in the {#link TopicPartition} is less than
* 0, no partition is set when publishing to the topic. The templates map keys are
* classes and the value the corresponding template to use for objects (producer
* record values) of that type. A {#link java.util.LinkedHashMap} is recommended when
* there is more than one template, to ensure the map is traversed in order. To send
* records with a null value, add a template with the {#link Void} class as a key;
* otherwise the first template from the map values iterator will be used.
* #param templates the {#link KafkaOperations}s to use for publishing.
* #param destinationResolver the resolving function.
*/
#SuppressWarnings("unchecked")
public DeadLetterPublishingRecoverer(Map<Class<?>, KafkaOperations<? extends Object, ? extends Object>> templates,
BiFunction<ConsumerRecord<?, ?>, Exception, TopicPartition> destinationResolver) {
I also need to work out how to hook this ErrorHandingDeserialiser into the ConsumerFactory.
Just set the appropriate properties - see the documentation.
I'm using spring boot 2.1.7.RELEASE and spring-kafka 2.2.8.RELEASE. We are in the process of upgrading the spring boot version but for now, we are using this spring-kafka version.
And I'm using #KafkaListener annotation to create a consumer and I'm using all default settings for the consumer.And I'm using below configuration as specified in the Spring-Kafka documentation.
// other props
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer2.class);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer2.class);
props.put(ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS, StringDeserializer.class);
props.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, AvroDeserializer.class.getName());
return new DefaultKafkaConsumerFactory<>(props);
Now, I've implemented my custom SeekToCurrentErrorHandler by extending SeekToCurrentErrorHandler to capture the send the records causing deserialization exception and send them to DLT.
Now the problem is, when i'm trying to test this logic with 30 messages with alternate messages having the deserialization exception, the list of the handle method is getting all 30 messages instead of getting only 15 messages which are causing the exception. With that said, how can i get the records with exception? Please suggest.
Here is my custom SeekToCurrentErrorHandler code
#Component
public class MySeekToCurrentErrorHandler extends SeekToCurrentErrorHandler {
private final MyDeadLetterRecoverer deadLetterRecoverer;
#Autowired
public MySeekToCurrentErrorHandler(MyDeadLetterRecoverer deadLetterRecoverer) {
super(-1);
this.deadLetterRecoverer = deadLetterRecoverer;
}
#Override
public void handle(Exception thrownException, List<ConsumerRecord<?, ?>> data, Consumer<?, ?> consumer, MessageListenerContainer container) {
if (thrownException instanceof DeserializationException) {
//Improve to support multiple records
DeserializationException deserializationException = (DeserializationException) thrownException;
deadLetterRecoverer.accept(data.get(0), deserializationException);
ConsumerRecord<?, ?>. consumerRecord = data.get(0);
sout(consumerRecord.key());
sout(consumerRecord.value());
} else {
//Calling super method to let the 'SeekToCurrentErrorHandler' do what it is actually designed for
super.handle(thrownException, data, consumer, container);
}
}
}
We have to pass all the remaining records, so that the STCEH can re-seek all partitions for the records that weren't processed.
After you recover the failed record, use SeekUtils to seek the remaining records (remove the one that you have recovered from the list).
Set recoverable to false so that doSeeks() doesn't try to recover the new first record.
/**
* Seek records to earliest position, optionally skipping the first.
* #param records the records.
* #param consumer the consumer.
* #param exception the exception
* #param recoverable true if skipping the first record is allowed.
* #param skipper function to determine whether or not to skip seeking the first.
* #param logger a {#link Log} for seek errors.
* #return true if the failed record was skipped.
*/
public static boolean doSeeks(List<ConsumerRecord<?, ?>> records, Consumer<?, ?> consumer, Exception exception,
boolean recoverable, BiPredicate<ConsumerRecord<?, ?>, Exception> skipper, Log logger) {
You won't need all this code when you move to a more recent version (Boot 2.1 and Spring for Apache Kafka 2.2 are no longer supported).
I've a custom dialog with several UI elements. Some TextFields are for numeric input. This dialog does not close when the escape key is hit and the focus is on any of the numeric text fields. The dialog closes fine when focus is on other TextFields which do not have this custom TextFormatter.
Here's the simplified code:
package application;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
TextField name = new TextField();
HBox hb1 = new HBox();
hb1.getChildren().addAll(new Label("Name: "), name);
TextField id = new TextField();
id.setTextFormatter(getNumberFormatter()); // numbers only
HBox hb2 = new HBox();
hb2.getChildren().addAll(new Label("ID: "), id);
VBox vbox = new VBox();
vbox.getChildren().addAll(hb1, hb2);
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setTitle("Number Escape");
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
dialog.getDialogPane().setContent(vbox);
Platform.runLater(() -> name.requestFocus());
if (dialog.showAndWait().get() == ButtonType.OK) {
System.out.println("OK: " + name.getText() + id.getText());
} else {
System.out.println("Cancel");
}
} catch (Exception e) {
e.printStackTrace();
}
}
TextFormatter<Number> getNumberFormatter() {
// from https://stackoverflow.com/a/31043122
DecimalFormat format = new DecimalFormat("#");
TextFormatter<Number> tf = new TextFormatter<>(c -> {
if (c.getControlNewText().isEmpty()) {
return c;
}
ParsePosition parsePosition = new ParsePosition(0);
Object object = format.parse(c.getControlNewText(), parsePosition);
if (object == null || parsePosition.getIndex() < c.getControlNewText().length()) {
return null;
} else {
return c;
}
});
return tf;
}
public static void main(String[] args) {
launch(args);
}
}
How do I close the dialog when escape key is hit while focus is on id?
The Problem
Before offering a solution I think it's important, or at least interesting, to understand why having a TextFormatter seems to change the behavior of the Dialog. If this doesn't matter to you, feel free to jump to the end of the answer.
Cancel Buttons
According to the documentation of Button, a cancel button is:
the button that receives a keyboard VK_ESC press, if no other node in the scene consumes it.
The end of that sentence is the important part. The way cancel buttons, as well as default buttons, are implemented is by registering an accelerator with the Scene that the Button belongs to. These accelerators are only invoked if the appropriate KeyEvent bubbles up to the Scene. If the event is consumed before it reaches the Scene, the accelerator is not invoked.
Note: To understand more about event processing in JavaFX, especially terms such as "bubbles" and "consumed", I suggest reading this tutorial.
Dialogs
A Dialog has certain rules regarding how and when it can be closed. These rules are documented here, in the Dialog Closing Rules section. Suffice to say, basically everything depends on which ButtonTypes have been added to the DialogPane. In your example you use one of the predefined types: ButtonType.CANCEL. If you look at the documentation of that field, you'll see:
A pre-defined ButtonType that displays "Cancel" and has a ButtonBar.ButtonData of ButtonBar.ButtonData.CANCEL_CLOSE.
And if you look at the documentation of ButtonData.CANCEL_CLOSE, you'll see:
A tag for the "cancel" or "close" button.
Is cancel button: True
What this means, at least for the default implementation, is that the Button created for said ButtonType.CANCEL will be a cancel button. In other words, the Button will have its cancelButton property set to true. This is what allows one to close a Dialog by pressing the Esc key.
Note: It's the DialogPane#createButton(ButtonType) method that's responsible for creating the appropriate button (and can be overridden for customization). While the return type of that method is Node it is typical, as documented, to return an instance of Button.
The TextFormatter
Every control in (core) JavaFX has three components: the control class, the skin class, and the behavior class. The latter class is responsible for handling user input, such as mouse and key events. In this case, we care about TextInputControlBehavior and TextFieldBehavior; the former is the superclass of the latter.
Note: Unlike the skin classes, which became public API in JavaFX 9, the behavior classes are still private API as of JavaFX 12.0.2. Much of what's described below are implementation details.
The TextInputControlBehavior class registers an EventHandler that reacts to the Esc key being pressed, invoking the cancelEdit(KeyEvent) method of the same class. All the base implementation of this method does is forward the KeyEvent to the TextInputControl's parent, if it has one—resulting in two event dispatching cycles for some unknown (to me) reason. However, the TextFieldBehavior class overrides this method:
#Override
protected void cancelEdit(KeyEvent event) {
TextField textField = getNode();
if (textField.getTextFormatter() != null) {
textField.cancelEdit();
event.consume();
} else {
super.cancelEdit(event);
}
}
As you can see, the presence of a TextFormatter causes the KeyEvent to be unconditionally consumed. This stops the event from reaching the Scene, the cancel button is not fired, and thus the Dialog does not close when the Esc key is pressed while the TextField has the focus. When there is no TextFormatter the super implementation is invoked which, as stated before, simply forwards the event to the parent.
The reason for this behavior is hinted at by the call to TextInputControl#cancelEdit(). That method has a "sister method" in the form of TextInputControl#commitValue(). If you look at the documentation of those two methods, you'll see:
If the field is currently being edited, this call will set text to the last commited value.
And:
Commit the current text and convert it to a value.
Respectively. That doesn't explain much, unfortunately, but if you look at the implementation their purpose becomes clear. A TextFormatter has a value property which is not updated in real time while typing into the TextField. Instead, the value is only updated when it's committed (e.g. by pressing Enter). The reverse is also true; the current text can be reverted to the current value by cancelling the edit (e.g. by pressing Esc).
Note: The conversion between String and an object of arbitrary type is handled by the StringConverter associated with the TextFormatter.
When there's a TextFormatter, the act of cancelling the edit is deemed an event-consuming scenario. This makes sense, I suppose. However, even when there's nothing to cancel the event is still consumed—this doesn't make as much sense to me.
A Solution
One way to fix this is to dig into the internals, using reflection, as is shown in kleopatra's answer. Another option is to add an event filter to the TextField or some ancestor of the TextField that closes the Dialog when the Esc key is pressed.
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
event.consume();
dialog.close();
}
});
If you'd like to include the cancel-edit behavior (cancel without closing) then you should only close the Dialog if there's no edit to cancel. Take a look at kleopatra's answer to see how one might determine whether or not a cancel is needed. If there is something to cancel simply don't consume the event and don't close the Dialog. If there isn't anything to cancel then just do the same as the code above (i.e. consume and close).
Is using an event filter the "recommended way"? It's certainly a valid way. JavaFX is event-driven like most, if not all, mainstream UI toolkits. For JavaFX specifically that means reacting to Events or observing Observable[Value]s for invalidations/changes. A framework built "on top of" JavaFX may add its own mechanisms. Since the problem is an event being consumed when we don't want it to be, it is valid to add your own handlers to implement the desired behavior.
The question already has an excellent answer, nothing to add. Just wanted to demonstrate how to tweak the behavior's InputMap to inject/replace our own mappings (as a follow-up to my comment). Beware: it's dirty in reflectively accessing a skin's behavior (private final field) and using internal api (Behavior/InputMap didn't make it into public, yet).
As Slaw pointed out, it's the behavior that prevents the ESCAPE from bubbling up to the cancel button if the TextField has a TextFormatter installed. IMO, it's not misbehaving in that case, just overshooting: the cancel/default buttons should be triggered on ESCAPE/ENTER if and only if no other had used it to change the state of the any input nodes (my somewhat free interpretation of consumed - had done some research on general UX guidelines that I can't find right now, embarassingly ...)
Applied to a form containing both a textField with textFormatter and a cancel button (aka: isCancelButton is true)
if the textField has uncommitted text, a cancel should revert the edit to the most recent committed value and consume the event
if the textField is committed it should let it bubble up to trigger the cancel button
The implementation of cancelEdit in behavior doesn't distinguish between those two states, but always consumes it. The example below implements the expected (by me, at least) behavior. It has
a helper method to decide whether or not is dirty (aka: the textField has an uncommitted edit)
a event handling method that checks for dirtyness, calls cancel and consumes the event only it had been dirty
a configuration method that tweaks the textFields inputMap such that the mapping is replaced by our own.
Note that this is a PoC: doesn't belong into helpers but into a custom skin (at the very least, ideally should be done by the behavior). And it is missing similar support for the ENTER .. which is slightly more involved because it has to take actionHandlers into account (which behavior tries to but fails to achieve)
To test the example:
compile (note: you need to reflectively access a private field, use whatever you have at hand - we all do, don't we) and run
type something into the field
press escape: the field's text is reverted to its initial value
press escape again: the cancel button is triggered
The example code:
public class TextFieldCancelSO extends Application {
/**
* Returns a boolean to indicate whether the given field has uncommitted
* changes.
*
* #param <T> the type of the formatter's value
* #param field the field to analyse
* #return true if the field has a textFormatter with converter and
* uncommitted changes, false otherwise
*/
public static <T> boolean isDirty(TextField field) {
TextFormatter<T> textFormatter = (TextFormatter<T>) field.getTextFormatter();
if (textFormatter == null || textFormatter.getValueConverter() == null) return false;
String fieldText = field.getText();
StringConverter<T> valueConverter = textFormatter.getValueConverter();
String formatterText = valueConverter.toString(textFormatter.getValue());
// todo: handle empty string vs. null value
return !Objects.equals(fieldText, formatterText);
}
/**
* Install a custom keyMapping for ESCAPE in the inputMap of the given field.
* #param field the textField to configure
*/
protected void installCancel(TextField field) {
// Dirty: reflectively access the behavior
// needs --add-exports at compile- and runtime!
// note: FXUtils is a custom helper class not contained in core fx, use your own
// helper or write the field access code as needed.
TextFieldBehavior behavior = (TextFieldBehavior) FXUtils.invokeGetFieldValue(
TextFieldSkin.class, field.getSkin(), "behavior");
// Dirty: internal api/classes
InputMap inputMap = behavior.getInputMap();
KeyBinding binding = new KeyBinding(KeyCode.ESCAPE);
// custom mapping that delegates to helper method
KeyMapping keyMapping = new KeyMapping(binding, e -> {
cancelEdit(field, e);
});
// by default, mappings consume the event - configure not to
keyMapping.setAutoConsume(false);
// remove old
inputMap.getMappings().remove(keyMapping);
// add new
inputMap.getMappings().add(keyMapping);
}
/**
* Custom EventHandler that's mapped to ESCAPE.
*
* #param field the field to handle a cancel for
* #param ev the received keyEvent
*/
protected void cancelEdit(TextField field, KeyEvent ev) {
boolean dirty = isDirty(field);
field.cancelEdit();
if (dirty) {
ev.consume();
}
}
private Parent createContent() {
TextFormatter<String> fieldFormatter = new TextFormatter<>(
TextFormatter.IDENTITY_STRING_CONVERTER, "textField ...");
TextField field = new TextField();
field.setTextFormatter(fieldFormatter);
// listen to skin: behavior is available only after it's set
field.skinProperty().addListener((src, ov, nv) -> {
installCancel(field);
});
// just to see the state of the formatter
Label fieldValue = new Label();
fieldValue.textProperty().bind(fieldFormatter.valueProperty());
// add cancel button
Button cancel = new Button("I'm the cancel");
cancel.setCancelButton(true);
cancel.setOnAction(e -> LOG.info("triggered: " + cancel.getText()));
HBox fields = new HBox(100, field, fieldValue);
BorderPane content = new BorderPane(fields);
content.setBottom(cancel);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TextFieldCancelSO.class.getName());
}
I have my MainActivity and inside that I have a number of fragments. I also have another activity that works as my launcher and does everything to do with the Google Drive section of my app. On start up this activity launches, connects to Drive and then launches the MainActivity. I have a button in one of my fragments that, when pushed, needs to call a method in the DriveActivity. I can't create a new instance of DriveActivity because then googleApiClient will be null. Is this possible and how would I go about doing it? I've already tried using getActivity and casting but I'm assuming that isn't working because DriveActivity isn't the fragments parent.
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//TODO for test only remove
directory = new Directory(SDCARD + LOCAL_STORAGE);
byte[] zippedFile = directory.getZippedFile(SDCARD + STORAGE_LOCATION + directory.getZipFileName());
//Here I need to somehow call DriveActivity.uploadFileToDrive(zippedFile);
//((DriveActivity)getActivity()).uploadFileToDrive(zippedFile);
}
});
Right, so I'm having a bit of difficulty with the heirarchy but I think what you want to do is define a method in the fragment that the activity will be required to override to use.
This will allow you to press the button, and then fire a method whos actual implementation is inside the parent.
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onItemSelected(String id);
}
example implementation:
private static Callbacks sDummyCallbacks = new Callbacks() {
#Override
public void onItemSelected(String id) {
//Button fired logic
}
};
so in the child you'd do just call:
this.onItemSelected("ID of Class");
EDITED
In retrospect what I believe you need is an activity whos sole purpose is to upload files, not fire off other activities.
Heres an example of a 'create file' activity:Google Demo for creating a file on drive
Heres an example of the 'base upload' activity' Base Service creator