I am trying to integrate a Native UI component in react-native using the new architecture with fabric enabled
Here is my spec file
import type {HostComponent, ViewProps} from 'react-native';
import type {
DirectEventHandler,
BubblingEventHandler,
} from 'react-native/Libraries/Types/CodegenTypes';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
type Event = Readonly<{
text?: string;
}>;
interface NativeProps extends ViewProps {
text: string;
onClickHandler?: DirectEventHandler<Event>; ////Event name should start with on
}
export default codegenNativeComponent<NativeProps>(
'MyButtonView',
) as HostComponent<NativeProps>;
Then on native side I created following files
public class MyButtonViewManager extends SimpleViewManager<MyButtonView> {
public static final String NAME = "MyButtonView";
ReactApplicationContext mCallerContext;
public MyButtonViewManager(ReactApplicationContext reactContext) {
mCallerContext = reactContext;
}
#NonNull
#Override
public String getName() {
return NAME;
}
#NonNull
#Override
protected MyButtonView createViewInstance(#NonNull ThemedReactContext reactContext) {
return new MyButtonView(reactContext);
}
#ReactProp(name = "text")
public void setQrCodeText(MyButtonView view, String text) {
view.setText(text);
}
#Nullable
#Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of("topOnClickHandler",
MapBuilder.of("registrationName", "onClickHandler")
);
}
}
public class MyButtonView extends androidx.appcompat.widget.AppCompatButton {
public MyButtonView(Context context) {
super(context);
configureViews();
}
private void configureViews(){
setBackgroundColor(Color.YELLOW);
setOnClickListener(view -> {
ReactContext reactContext = (ReactContext)getContext();
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(
reactContext ,getId()
);
eventDispatcher.dispatchEvent(new MyButtonClickEvent(getId()));
});
}
}
public class MyButtonClickEvent extends Event<MyButtonClickEvent> {
public MyButtonClickEvent(int viewId) {
super(viewId);
}
#Override
public String getEventName() {
return "topOnClickHandler";
}
#Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
super.dispatch(rctEventEmitter);
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap());
}
#Nullable
#Override
protected WritableMap getEventData() {
WritableMap event = Arguments.createMap();
event.putString("message", "MyMessage");
return event;
}
}
What is the alternative to dispatch and RCTEventEmitter as both are deprecated? I was looking into RCTModernEventEmitter and it also extends the deprecated RCTEventEmitter
Also i have to change the event name from OnClickHandler to topOnClickHandler in Native Android side. It was throwing hermes error. Not sure why there should be top prefix, why can't it just be OnClickHandler.
From the react-native source code:
This [RCTModernEventEmitter] is a transitional replacement for RCTEventEmitter that works with Fabric and non-Fabric renderers. RCTEventEmitter works with Fabric as well, but there are negative perf implications and it should be avoided.
You can use this for now as a replacement that works with Fabric.
This [RCTModernEventEmitter] interface will also be deleted in the distant future and be replaced with a new interface that doesn't need the old receiveEvent method at all. But for the foreseeable future, this is the recommended interface to use for EventEmitters.
However, in the long run this will be removed.
There's ReactEventEmitter that might help based on your use-case.
Related
here is my problem:
i have used MVVM/Repository design pattern like this:
Activity -(Observes)-> ViewModel's LiveData -> Repository -> WebService API (GET Resource)
i have another calls for UPDATING Resource to WebService.
Problem:
after changing resource on the server. how i can make the Resource livedata to update itself with new servers data
i want to force it fetch data from server again because some other data may have been changed.
and i dont want to use local database (Room) and change it because my server data might be changed. and they need to fetch each time.
The Only solution passed my Mind was to create a Livedata Source (as dataVersion) to it.
and increment it after every update like this (pseudo code):
dataVersion = new MutableLiveData();
dataVersion.setValue(0);
// my repository get method hasnt anything to do with the dataVersion.
myData = Transformation.switchmap(dataVersion, versionNum -> { WebServiceRepo.getList() });
and how dataVersion should get updated in ViewModel.
You could extend MutableLiveData to give it manual fetch functionality.
public class RefreshLiveData<T> extends MutableLiveData<T> {
public interface RefreshAction<T> {
private interface Callback<T> {
void onDataLoaded(T t);
}
void loadData(Callback<T> callback);
}
private final RefreshAction<T> refreshAction;
private final Callback<T> callback = new RefreshAction.Callback<T>() {
#Override
public void onDataLoaded(T t) {
postValue(t);
}
};
public RefreshLiveData(RefreshAction<T> refreshAction) {
this.refreshAction = refreshAction;
}
public final void refresh() {
refreshAction.loadData(callback);
}
}
Then you can do
public class YourViewModel extends ViewModel {
private RefreshLiveData<List<Project>> refreshLiveData;
private final GithubRepository githubRepository;
private final SavedStateHandle savedStateHandle;
public YourViewModel(GithubRepository githubRepository, SavedStateHandle savedStateHandle) {
this.githubRepository = githubRepository;
this.savedStateHandle = savedStateHandle;
refreshLiveData = Transformations.switchMap(savedStateHandle.getLiveData("userId", ""), (userId) -> {
githubRepository.getProjectList(userId);
});
}
public void refreshData() {
refreshLiveData.refresh();
}
public LiveData<List<Project>> getProjects() {
return refreshLiveData;
}
}
And then repository can do:
public RefreshLiveData<List<Project>> getProjectList(String userId) {
final RefreshLiveData<List<Project>> liveData = new RefreshLiveData<>((callback) -> {
githubService.getProjectList(userId).enqueue(new Callback<List<Project>>() {
#Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
callback.onDataLoaded(response.body());
}
#Override
public void onFailure(Call<List<Project>> call, Throwable t) {
}
});
});
return liveData;
}
I try to make sample login page with two fields (username, password) and save button with android architecture component, using android data binding, validating the data in viewmodel and from view model I make call to repository for remote server call as mentioned in official doc, remote server return me userid with success so how can I start new fragment from view model using this success? I learn something about singleLiveEvent and EventObserver, but I'm not able to find there clear usage example:
LoginViewModel
private MutableLiveData<String> snackbarStringSingleLiveEvent= new MutableLiveData<>();
#Inject
public LoginViewModel(#NonNull AppDatabase appDatabase,
#NonNull JobPortalApplication application,
#NonNull MyApiEndpointInterface myApiEndpointInterface) {
super(application);
loginRepository = new LoginRepository(application, appDatabase, myApiEndpointInterface);
snackbarStringSingleLiveEvent = loginRepository.getLogin(username.get(), password.get(), type.get());
}
public MutableLiveData<String> getSnackbarStringSingleLiveEvent() {
return snackbarStringSingleLiveEvent;
}
Repository
public SingleLiveEvent<String> getLogin(String name, String password, String type) {
SingleLiveEvent<String> mutableLiveData = new SingleLiveEvent<>();
apiEndpointInterface.getlogin(name, password, type).enqueue(new Callback<GenericResponse>() {
#Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(response.body().getMessage());
}
#Override
public void onFailure(Call<GenericResponse> responseCall, Throwable t) {
mutableLiveData.setValue(Constant.FAILED);
}
});
return mutableLiveData;
}
Login Fragment
private void observeViewModel(final LoginViewModel viewModel) {
// Observe project data
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new Observer<String>() {
#Override
public void onChanged(String s) {
}
});
}
How can I use EventObserver in above case? Any practical example?
Check out below example about how you can create single LiveEvent to observe only one time as LiveData :
Create a class called Event as below that will provide our data once and acts as child of LiveData wrapper :
public class Event<T> {
private boolean hasBeenHandled = false;
private T content;
public Event(T content) {
this.content = content;
}
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
public boolean isHandled() {
return hasBeenHandled;
}
}
Then declare this EventObserver class like below so that we don't end up placing condition for checking about Event handled every time, everywhere :
public class EventObserver<T> implements Observer<Event<T>> {
private OnEventChanged onEventChanged;
public EventObserver(OnEventChanged onEventChanged) {
this.onEventChanged = onEventChanged;
}
#Override
public void onChanged(#Nullable Event<T> tEvent) {
if (tEvent != null && tEvent.getContentIfNotHandled() != null && onEventChanged != null)
onEventChanged.onUnhandledContent(tEvent.getContentIfNotHandled());
}
interface OnEventChanged<T> {
void onUnhandledContent(T data);
}
}
And How you can implement it :
MutableLiveData<Event<String>> data = new MutableLiveData<>();
// And observe like below
data.observe(lifecycleOwner, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
// And this is how you add data as event to LiveData
data.setValue(new Event(""));
Refer here for details.
Edit for O.P.:
Yes, data.setValue(new Event("")); is meant for repository when you've got response from API (Remember to return same LiveData type you've taken in VM instead of SingleLiveEvent class though).
So, let's say you've created LiveData in ViewModel like below :
private MutableLiveData<Event<String>> snackbarStringSingleLiveEvent= new MutableLiveData<>();
You provide value to this livedata as Single Event from repository like below :
#Override
public void onResponse(Call<GenericResponse> call, Response<GenericResponse> response) {
mutableLiveData.setValue(new Event(response.body().getMessage())); // we set it as Event wrapper class.
}
And observe it on UI (Fragment) like below :
viewModel.getSnackbarStringSingleLiveEvent().observe(this, new EventObserver<String>(data -> {
// your unhandled data would be here for one time.
}));
Event.java
public class Event<T> {
private T content;
private boolean hasBeenHandled = false;
public Event(T content) {
this.content = content;
}
/**
* Returns the content and prevents its use again.
*/
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return content;
}
}
/**
* Returns the content, even if it's already been handled.
*/
public T peekContent() {
return content;
}
}
EventObserver.java
public class EventObserver<T> implements Observer<Event<? extends T>> {
public interface EventUnhandledContent<T> {
void onEventUnhandledContent(T t);
}
private EventUnhandledContent<T> content;
public EventObserver(EventUnhandledContent<T> content) {
this.content = content;
}
#Override
public void onChanged(Event<? extends T> event) {
if (event != null) {
T result = event.getContentIfNotHandled();
if (result != null && content != null) {
content.onEventUnhandledContent(result);
}
}
}
}
Example, In ViewModel Class
public class LoginViewModel extends BaseViewModel {
private MutableLiveData<Event<Boolean>> _isProgressEnabled = new MutableLiveData<>();
LiveData<Event<Boolean>> isProgressEnabled = _isProgressEnabled;
private AppService appService;
private SchedulerProvider schedulerProvider;
private SharedPreferences preferences;
#Inject
LoginViewModel(
AppService appService,
SchedulerProvider schedulerProvider,
SharedPreferences preferences
) {
this.appService = appService;
this.schedulerProvider = schedulerProvider;
this.preferences = preferences;
}
public void login(){
appService.login("username", "password")
.subscribeOn(schedulerProvider.executorIo())
.observeOn(schedulerProvider.ui())
.subscribe(_userLoginDetails::setValue,
_userLoginDetailsError::setValue,
() -> _isProgressEnabled.setValue(new Event<>(false)),
d -> _isProgressEnabled.setValue(new Event<>(true))
)
}
}
In Login Fragment,
viewModel.isProgressEnabled.observe(this, new EventObserver<>(hasEnabled -> {
if (hasEnabled) {
// showProgress
} else {
// hideProgress
}
}));
Using Event and EventObserver class we can achieve the same like SingleLiveEvent class but if you are thinking a lot of boilerplate code just avoid this method. I hope it would help you and give some idea about why we are using SingleEvent in LiveData.
I understand that Google gives the guidelines to use LiveData between the ViewModel and UI but there are edge cases where using LiveData as a SingleLiveEvent is like reinventing the wheel. For single time messaging between the view model and user interface we can use the delegate design pattern. When initializing the view model in the activity we just have to set the activity as the implementer of the interface. Then throughout our view model we can call the delegate method.
Interface
public interface Snackable:
void showSnackbarMessage(String message);
UI
public class MyActivity extends AppCompatActivity implements Snackable {
private MyViewModel myViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout);
this.myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
this.myViewModel.setListener(this);
}
#Override
public void showSnackbarMessage(String message) {
Toast.makeText(this, "message", Toast.LENGTH_LONG).show();
}
}
View Model
public class MyViewModel extends AndroidViewModel {
private Snackable listener;
public MyViewModel(#NonNull Application application) {
super(application);
}
public void setListener(MyActivity activity){
this.listener = activity;
}
private void sendSnackbarMessage(String message){
if(listener != null){
listener.showSnackbarMessage(message);
}
}
private void anyFunctionInTheViewModel(){
sendSnackbarMessage("Hey I've got a message for the UI!");
}
}
I created string in java:
import java.lang.Math;
import java.io.*;
import java.util.*;
public class Test {
public int measureConcentration(double[] means){
return (int) ((means[3] / means[1]) * 100) ;
}
public static void main(String[] args) {
int score = measureConcentration(bandMeans);
String ratio = Integer.toString(score);
// Print values
System.out.println(String.valueOf(ratio));
I would like to display it in react native. But when I try to compile app in powershell I receive an error due to cannot find symbol int score = measureConcentration(bandMeans);.
What am I doing wrong?
I've followed React Native docs https://facebook.github.io/react-native/docs/native-modules-android.html#callbacks to create Android native module.
To expose a method to JavaScript a Java method must be annotated using #ReactMethod. The return type of bridge methods is always void. React Native bridge is asynchronous, so the only way to pass a result to JavaScript is by using callbacks or emitting events
public class TestBridge extends ReactContextBaseJavaModule {
public TestBridge(ReactApplicationContext reactContext) {
super(reactContext);
}
#Override
public String getName() {
return "TestBridge";
}
#ReactMethod
public void getString(Callback stringCallback) {
stringCallback.invoke("Native module String");
}
}
and
public class TestBridgePackage implements ReactPackage {
#Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new TestBridge(reactContext));
return modules;
}
#Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
insert my package in MainApplication
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
...
#Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new TestBridgePackage()
);
}
...
};
...
}
And then get a string on React Native side with callback
import {NativeModules} from 'react-native';
export default class App extends Component<Props> {
componentWillMount() {
const TestBridge = NativeModules.TestBridge;
TestBridge.getString(string => console.log(string));
}
}
Have you tried
import android.util.Log;
and then using
Log.d("first string goes here", "second string goes here");
Recently I came across a problem which I was asked to design using appropriate design patterns. The proble statement is:
Implement a remote control of TV.
Any remote control we use, either at home/hotel or at a friend’s place,
we just pick up the TV remote control and start pressing Up and Down
or Forward and Back keys to change the channels.
Choose a suitable design pattern for implementation of this problem.
I am not able to figure out how to design this ask. This is what I came up with:
Place is an abstract class.
Home extends Place
Hotel extends Place
FriendPlace extends Place
TVRemote is a class
Place has a TVRemote
Keys is an interface
Keys has a method press()
UpKey, DownKey, ForwardKey, BackKey are classes implementing Keys
TVRemote has Keys
There could be more Keys in TVRemote
This is what I could think of but unable to incorporate a Design Pattern here. Any guidance?
A simplistic approach will be to create an interface
interface RemoteControl
{
public void up();
public vois down();
public void forward();
public void back();
}
and then create specific classes that will implement that interface for specific devices
e.g.
public class HomeRemote implements RemoteControl {
public void up(){
..
}
public vois down(){
..
}
public void forward(){
..
}
public void back(){
..
}
}
However
After our discussion - and after searching a little bit more, i am inclined to think now that Bridge pattern is what is asked for here.
Check this out - http://www.programcreek.com/2011/10/java-design-pattern-bridge/
There abstract class for remote control is used with basic implementation of (up,down,forward,back)
Then each specific TVRemote extends the abstract class to add more/and device specific functionality.
Also note that TVs are using common interface where (goUp(),goDown(),goForward(),goBack() and possibly on(),off()) functions are described.
Some observations:
different remote controls may have different number of buttons
different buttons execute different actions
remote control should be oblivious of the details how the action is executed
one should be able to reprogram remote control either by assigning different actions to buttons or by supporting different devices
The most straightforward pattern to use with this situation is Command. One could create specific Command implementations and then assign Commands to buttons:
public interface Command {
void Execute();
}
public class Button {
private readonly Command command;
public Button(Command command) {
this.command = command;
}
public void Press() {
this.command.Execute();
}
}
public class Remote {
public Button ButtonPlaceholder1 { get; set; }
public Button ButtonPlaceholder2 { get; set; }
public Button ButtonPlaceholder3 { get; set; }
public Button ButtonPlaceholder4 { get; set; }
}
So, what would be the benefit of having Button class? Well, let's say you want to introduce a slider button, which can be moved Up and Down. In this case, you will configure it with two Commands:
public class SliderButton {
public SliderButton(Command up, Command down) {
this.commandUp = up;
this.commandDown = down;
}
public void Up() {
this.commandUp.Execute();
}
public void Down() {
this.commandDown.Execute();
}
}
And interesting follow-up question on this interview would be, "How to implement a button that would cancel the action made by pressing previous button? (e.g. I was watching ESPN channel, but there was a break in between a match, so I switched to MTV, but I want to check once in a while whether break has ended, and if not, go back to MTV)
You should use command pattern here. Usually it has Invoker, Client, Command and Receiver. Here are the classes you may require.
Command
public interface ICommand {
void execute();
}
Invoker
public class RemoteControl {
Map<Key, ICommand> commandsByKey;
public RemoteControl() {
commandsByKey = new HashMap<>();
}
public void setCommand(Key key, ICommand command) {
commandsByKey.put(key, command);
}
public void press(Key key) throws Exception {
ICommand command = commandsByKey.get(key);
if(command == null)
throw new Exception("Invalid Key");
command.execute();
}
}
Receiver
public class TV {
private String brand;
public TV(String brand) {
this.brand = brand;
}
#Override
public String toString() {
return brand + " TV";
}
}
Client
public abstract class Place {
private TV tv;
private RemoteControl remoteControl;
public Place(TV tv) {
this.tv = tv;
this.remoteControl = new RemoteControl();
remoteControl.setCommand(Key.UP, new UpCommand(this.tv));
remoteControl.setCommand(Key.FORWARD, new ForwardCommand(this.tv));
remoteControl.setCommand(Key.DOWN, new DownCommand(this.tv));
remoteControl.setCommand(Key.BACK, new BackCommand(this.tv));
}
public TV getTv() {
return tv;
}
public RemoteControl getRemoteControl() {
return remoteControl;
}
}
public class Home extends Place {
public Home() {
super(new TV("Sony"));
}
}
public class Hotel extends Place {
public Hotel() {
super(new TV("LG"));
}
}
Concrete Commands
public class UpCommand implements ICommand {
private TV tv;
public UpCommand(TV tv) {
this.tv = tv;
}
#Override
public void execute() {
System.out.println("Up Command - " + tv);
}
}
public class DownCommand implements ICommand {
private TV tv;
public DownCommand(TV tv) {
this.tv = tv;
}
#Override
public void execute() {
System.out.println("Down Command - " + tv);
}
}
public class ForwardCommand implements ICommand {
private TV tv;
public ForwardCommand(TV tv) {
this.tv = tv;
}
#Override
public void execute() {
System.out.println("Forward Command - " + tv);
}
}
public class BackCommand implements ICommand {
private TV tv;
public BackCommand(TV tv) {
this.tv = tv;
}
#Override
public void execute() {
System.out.println("Back Command - " + tv);
}
}
Keys
public enum Key {
UP, DOWN, FORWARD, BACK
}
TEST
public class RemoteTest {
public static void main(String[] args) throws Exception {
Place home = new Home();
home.getRemoteControl().press(Key.UP);
home.getRemoteControl().press(Key.DOWN);
home.getRemoteControl().press(Key.BACK);
Hotel hotel = new Hotel();
hotel.getRemoteControl().press(Key.UP);
}
}
If you add any additional keys to the remote you don't need to touch any of the existing command or invoker. You just need to add it in the client. This adheres to Open Close principle. If you have different remote for each place then make it as constructor argument, so you no need to change any other classes.
I am working on application in Vaadin for my classes.
I have to draw some map on the screen so I'm using gwt-graphics lib.
I have also some servlet which is waiting for requests.
When some specific request will come view of the map should be changed.
It lead me to prepare custom event:
// class NewModulePositionHandler
package com.example.locator;
import com.google.gwt.event.shared.EventHandler;
public interface NewModulePositionHandler extends EventHandler {
void onNewModulePosition(NewModulePositionEvent event);
}
Below implementation of my custom event:
import com.google.gwt.event.shared.GwtEvent;
public class NewModulePositionEvent extends GwtEvent<NewModulePositionHandler> {
private static final Type<NewModulePositionHandler> TYPE = new Type<NewModulePositionHandler>();
private final String m_Color;
public NewModulePositionEvent(String color) {
m_Color = color;
}
public static Type<NewModulePositionHandler> getType() {
return TYPE;
}
public String getColor() {
return m_Color;
}
#Override
public com.google.gwt.event.shared.GwtEvent.Type<NewModulePositionHandler> getAssociatedType() {
// TODO Auto-generated method stub
return TYPE;
}
#Override
protected void dispatch(NewModulePositionHandler handler) {
handler.onNewModulePosition(this);
}
}
And it's time for implementation of my custom widget:
a) MyComp.java
import com.google.gwt.event.shared.GwtEvent;
public class NewModulePositionEvent extends GwtEvent<NewModulePositionHandler> {
private static final Type<NewModulePositionHandler> TYPE = new Type<NewModulePositionHandler>();
private final String m_Color;
public NewModulePositionEvent(String color) {
m_Color = color;
}
public static Type<NewModulePositionHandler> getType() {
return TYPE;
}
public String getColor() {
return m_Color;
}
#Override
public com.google.gwt.event.shared.GwtEvent.Type<NewModulePositionHandler> getAssociatedType() {
// TODO Auto-generated method stub
return TYPE;
}
#Override
protected void dispatch(NewModulePositionHandler handler) {
handler.onNewModulePosition(this);
}
}
b) MyCompClientRpc.java
import com.vaadin.shared.communication.ClientRpc;
public interface MyCompClientRpc extends ClientRpc {
// TODO example API
public void alert(String message);
public void changeColor(String color);
}
c) MyCompConnector.java
package com.example.locator.widgetset.client.mycomp;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.shared.ui.Connect;
import com.example.locator.MyComp;
import com.example.locator.NewModulePositionEvent;
import com.example.locator.NewModulePositionHandler;
import com.example.locator.widgetset.client.mycomp.MyCompWidget;
import com.example.locator.widgetset.client.mycomp.MyCompServerRpc;
import com.vaadin.client.communication.RpcProxy;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.example.locator.widgetset.client.mycomp.MyCompClientRpc;
import com.example.locator.widgetset.client.mycomp.MyCompState;
import com.vaadin.client.communication.StateChangeEvent;
#Connect(MyComp.class)
public class MyCompConnector extends AbstractComponentConnector {
MyCompServerRpc rpc = RpcProxy
.create(MyCompServerRpc.class, this);
public MyCompConnector() {
registerRpc(MyCompClientRpc.class, new MyCompClientRpc() {
public void alert(String message) {
// TODO Do something useful
Window.alert(message);
}
public void changeColor(String color) {
getWidget().InitMap(color);
}
});
// TODO ServerRpc usage example, do something useful instead
getWidget().addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
final MouseEventDetails mouseDetails = MouseEventDetailsBuilder
.buildMouseEventDetails(event.getNativeEvent(),
getWidget().getElement());
rpc.clicked(mouseDetails);
}
});
getWidget().addNewModulePositionHandler(new NewModulePositionHandler() {
public void onNewModulePosition(NewModulePositionEvent event) {
// TODO Auto-generated method stub
rpc.newModulePosition(event.getColor());
}
});
}
#Override
protected Widget createWidget() {
return GWT.create(MyCompWidget.class);
}
#Override
public MyCompWidget getWidget() {
return (MyCompWidget) super.getWidget();
}
#Override
public MyCompState getState() {
return (MyCompState) super.getState();
}
#Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
// TODO do something useful
final String color = getState().color;
getWidget().InitMap(color);
}
}
d) MyCompServerRpc.java
package com.example.locator.widgetset.client.mycomp;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.communication.ServerRpc;
public interface MyCompServerRpc extends ServerRpc {
// TODO example API
public void clicked(MouseEventDetails mouseDetails);
public void newModulePosition(String color);
}
e) MyCompState.java
package com.example.locator.widgetset.client.mycomp;
public class MyCompState extends com.vaadin.shared.AbstractComponentState {
// TODO example state
public String color = "#000000";
}
And finally implementation of the widget:
f) MyCompWidget.java
package com.example.locator.widgetset.client.mycomp;
import org.vaadin.gwtgraphics.client.DrawingArea;
import org.vaadin.gwtgraphics.client.Line;
import org.vaadin.gwtgraphics.client.shape.Circle;
import org.vaadin.gwtgraphics.client.shape.Rectangle;
import com.example.locator.HasNewModulePositionHandlers;
import com.example.locator.NewModulePositionEvent;
import com.example.locator.NewModulePositionHandler;
import com.google.gwt.dev.util.collect.HashMap;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.touch.client.Point;
import com.example.locator.Module;
// TODO extend any GWT Widget
public class MyCompWidget extends DrawingArea implements HasNewModulePositionHandlers {
public static final String CLASSNAME = "mycomp";
public static double m_AreaWidth = 64.355;
public static double m_AreaHeight = 17.385;
public static int m_PictureWidth;
public static int m_PictureHeight;
public static double m_AreaToMapRatio;
public static double m_RouteWidth = 3.5;
public static double m_MainRouteCoordinateY = 8.0828;
public Circle circle;
//public HashMap<Integer, Module> ModuleMap = new HashMap<Integer, Module>();
public MyCompWidget(){
super(640, 320);
//ModuleMap.put(666, new Module(666, 30.0, 8.08, new Circle((int)TranslateCoordinate(30.0), (int)TranslateCoordinate(8.0), 7)));
//ModuleMap.put(15, new Module(15, 27.0, 8.08, new Circle((int)TranslateCoordinate(30.0), (int)TranslateCoordinate(8.0), 7)));
double xRatio = m_AreaWidth / 640;
double yRatio = m_AreaHeight / 320;
m_AreaToMapRatio = xRatio > yRatio ? xRatio : yRatio;
InitMap("#919491");
setStyleName(CLASSNAME);
}
public void InitMap(String color)
{
m_PictureWidth = (int)TranslateCoordinate(m_AreaWidth);
m_PictureHeight = (int)TranslateCoordinate(m_AreaHeight);
Rectangle rectangle = new Rectangle(0, 0, m_PictureWidth, m_PictureHeight);
rectangle.setFillColor(color);
add(rectangle);
Point point1Beg = new Point(0.0, 8.0828);
Point point1End = new Point(64.355, 8.0838);
Point point2Beg = new Point(20.2825, 8.0828);
Point point2End = new Point(20.2825, 17.385);
Point point3Beg = new Point(59.325, 0.0);
Point point3End = new Point(59.325, 8.0828);
point1Beg = TranslatePoint(point1Beg);
point1End = TranslatePoint(point1End);
point2Beg = TranslatePoint(point2Beg);
point2End = TranslatePoint(point2End);
point3Beg = TranslatePoint(point3Beg);
point3End = TranslatePoint(point3End);
Line line1 = new Line((int)point1Beg.getX(), (int)point1Beg.getY(), (int)point1End.getX(), (int)point1End.getY());
Line line2 = new Line((int)point2Beg.getX(), (int)point2Beg.getY(), (int)point2End.getX(), (int)point2End.getY());
Line line3 = new Line((int)point3Beg.getX(), (int)point3Beg.getY(), (int)point3End.getX(), (int)point3End.getY());
line1.setStrokeColor("#FFFFFF");
line2.setStrokeColor("#FFFFFF");
line3.setStrokeColor("#FFFFFF");
line1.setStrokeWidth((int)TranslateCoordinate(m_RouteWidth));
line2.setStrokeWidth((int)TranslateCoordinate(m_RouteWidth));
line3.setStrokeWidth((int)TranslateCoordinate(m_RouteWidth));
add(line1);
add(line2);
add(line3);
DrawWall(TranslateCoordinate(10.0));
DrawWall(TranslateCoordinate(20.0));
DrawWall(TranslateCoordinate(30.0));
DrawWall(TranslateCoordinate(40.0));
DrawWall(TranslateCoordinate(50.0));
DrawWall(TranslateCoordinate(60.0));
DrawDoor(3.0, 3.0);
DrawDoor(13.0, 3.0);
DrawDoor(23.0, 3.0);
DrawDoor(33.0, 3.0);
DrawDoor(43.0, 3.0);
DrawDoor(53.0, 3.0);
circle = new Circle((int)TranslateCoordinate(25.0), (int)TranslateCoordinate(8.0), 15);
add(circle);
}
public void DrawWall(double a_Place)
{
Line line = new Line((int)a_Place, 0, (int)a_Place, (int)TranslateCoordinate(m_AreaHeight));
line.setStrokeColor("#FFFFFF");
add(line);
}
public void DrawDoor(double a_Position, double a_Width)
{
double realDoorPositionY = m_MainRouteCoordinateY - (m_RouteWidth / 2);
int doorPositionYTop = (int)TranslateCoordinate(realDoorPositionY) - 1;
int doorPositionYBottom = (int)TranslateCoordinate(realDoorPositionY + m_RouteWidth) + 1;
Line line = new Line((int)TranslateCoordinate(a_Position), doorPositionYTop, (int)TranslateCoordinate(a_Position) + (int)TranslateCoordinate(a_Width), doorPositionYTop);
line.setStrokeColor("#000000");
line.setStrokeWidth(2);
add(line);
Line line2 = new Line((int)TranslateCoordinate(a_Position), doorPositionYBottom, (int)TranslateCoordinate(a_Position) + (int)TranslateCoordinate(a_Width), doorPositionYBottom);
line2.setStrokeColor("#000000");
line2.setStrokeWidth(2);
add(line2);
}
public Point TranslatePoint(Point a_Point)
{
Point translatedPoint = new Point(TranslateCoordinate(a_Point.getX()), TranslateCoordinate(a_Point.getY()));
return translatedPoint;
}
public double TranslateCoordinate(double a_Coordinate)
{
return (a_Coordinate) / (m_AreaToMapRatio);
}
public void Move(int id) {
//ModuleMap.get(id).GetCircle().setX(10 + circle.getX());
}
public HandlerRegistration addNewModulePositionHandler(
NewModulePositionHandler handler) {
return addHandler(handler, NewModulePositionEvent.TYPE);
}
private void someMethod() {
fireEvent(new NewModulePositionEvent("#000000"));
}
public void emulateEvent() {
someMethod();
}
}
g) HasNewModulesPositionHandlers.java
package com.example.locator;
import com.google.gwt.event.shared.HandlerRegistration;
public interface HasNewModulePositionHandlers {
// Attention! method returns HandlerRegistration, so that handler can be cancelled
public HandlerRegistration addNewModulePositionHandler(
NewModulePositionHandler handler);
}
If I compile the widgets set containing MyCompWidget and then run my application on glassfish I get the following message:
Widgetset 'com.example.locator.widgetset.LocatorWidgetset' does not contain implementation for com.example.locator.MyComp. Check its component connector's #Connect mapping, widgetsets GWT module description file and re-compile your widgetset. In case you have downloaded a vaadin add-on package, you might want to refer to add-on instructions.
If I cut
public void addNewModulePositionHandler(
NewModulePositionHandler handler) {
handlerManager.addHandler(NewModulePositionEvent.getType(), handler);
// TODO Auto-generated method stub
}
widget works properly (of course I have to comment out these lines from MyCompConnector as well):
getWidget().addNewModulePositionHandler(new NewModulePositionHandler() {
public void onNewModulePosition(NewModulePositionEvent event) {
// TODO Auto-generated method stub
rpc.newModulePosition(event.getColor());
}
});
Can anyone tell me where is the problem? It seems that compilation of the widget failes but I can't find any information about that.
Please, help me.
Thanks in advance.
Compilation problem is very clear: GWT compiler cannot fins source code for a specified java class. You add source files to GWT compiler scope in two steps:
*.java files must be accessible through classpath (eclipse gwt compiler automatically includes all source folders to classpath)
You need to tell GWT that it should consider your packages during compilation
Create .gwt.xml file in PARENT package (relative to your source files) with the following content (if your package is pkg1.pkg2.pkg3, then you should in package pkg1.pkg2 create file "Pkg3.gwt.xml", pkg = "pkg3", by convention pkg3 is typically named "client")
<module>
<source path="pkg3" />
</module>
!!!Be careful with letter cases, By convention those are the correct names.
Add to your widgetset.gwt.xml file "inherits" directive like that
<inherits name="pkg1.pkg2.Pkg3" />
!!! Pay attention to letter cases (P is capital in Pkg3, after file name Pkg3.gwt.xml), .gwt.xml is omitted here
Then, first of all, Vaadin uses GWT with a certain twist. I would recommend browsing through custom widgetset samples. You may need to understand how to propagate events to and from Vaadin-specific components. Below I explain how GWT event are supposed to be implemented and used. I'm not an expert in Vaadin, especially in advanced topics such as customizing widgetsets.
So, speaking of GWT.
Basically, you need to understand only two things to make (non-DOM, also knows as bitless) events work in GWT.
Code support
Event class. NewModulePositionEvent in your case.
Event Handler interface. NewModulePositionHandler in your case.
Feature interface (optional but advised). HasNewModulePositionHandlers in your case.
Usage pattern
Basically, the component that is supposed to fire event should create relevant Event object and pass it to the fireEvent method. All the logic to invoke necessary habdler is provided by EventBus (internally)
If you need to provide API to fire events externally (such as click() method for a Button), it should be done by providing special methods (do not expose internal stuff how exactly event is fired)
DOM events are special in details how they are created (by browser) and dispatched (they need to be explicitly enabled). Anyway, all the browser events are already implemented in GWT out of the box.
So, typical implementations for the above items: For the sake of clarity, I provide excerpts from our production code.
1) Event class
public class QuestionClickEvent extends GwtEvent<QuestionClickHandler> {
public QuestionClickEvent(SScript script, SQuestion question) {
super();
this.script = script;
this.question = question;
}
public static final Type<QuestionClickHandler> TYPE = new Type<QuestionClickHandler>();
// internal event state
private final SScript script;
private final SQuestion question;
#Override
public Type<QuestionClickHandler> getAssociatedType() {
return TYPE;
}
#Override
protected void dispatch(QuestionClickHandler handler) {
handler.onQuestionClicked(this);
}
// provide access to internal event state
public SScript getScript() {
return script;
}
public SQuestion getQuestion() {
return question;
}
}
2) Handler interface
public interface QuestionClickHandler extends EventHandler {
public void onQuestionClicked(QuestionClickEvent event);
}
3) Feature interface
public interface HasQuestionClickHandlers {
// Attention! method returns HandlerRegistration, so that handler can be cancelled
public HandlerRegistration addQuestionClickHandler(
QuestionClickHandler handler);
}
4) Your widget/component
public class SummaryPanel extends Widget implements HasQuestionClickHandlers {
// blah-blah-blah
// implement your handler registration method
#Override
public HandlerRegistration addQuestionClickHandler(
QuestionClickHandler handler) {
return addHandler(handler, QuestionClickEvent.TYPE);
}
// blah-blah-blah
private someMethod() {
// suddenly you realized that you need to to fire your event
fireEvent(new QuestionClickEvent(script, question));
}
// sample external API method
public void emulateEvent() {
someMethod();
}
}
And finally usage example:
SummaryPanel summary = new SummaryPanel();
summary.addQuestionClickHandler(new QuestionClickHandler() {
#Override
public void onQuestionClicked(QuestionClickEvent event) {
// your reaction goes here
}
});