gwt add custom widget to panel - gwt

I have created some classes in which it create a bunch of widgets (e.g. label, textbox). I want to create the widget on the fly and add it to a panel. How can I do that.

Assuming you use HorizontalPanel, VerticalPanel, FlowPanel or some other panel with an add(Wiget) method, you would simply invoke add(myWidget);
final VerticalPanel panel = new VerticalPanel();
final Button sendButton = new Button("Add widget");
panel.add(sendButton);
sendButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
panel.add(new Label(new Date().toString()));
}
});
RootPanel.get().add(panel);
An alternative may be use the setVisible(boolean) to show and hide widgets instead of adding and removing them.
final VerticalPanel panel = new VerticalPanel();
final Button sendButton = new Button("Toggle visibility");
panel.add(sendButton);
final Label label = new Label(new Date().toString());
panel.add(label);
sendButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
label.setVisible(!label.isVisible());
}
});
RootPanel.get().add(panel);

Related

How to force stateful widget redraw using keys?

I'm making an app that pulls data from an API and displays it in a view (MVC style).
I need to figure out how to force my view widget to redraw itself. Right now I tried with ValueKeys and ObjectKeys but to no avail.
There's lots and lots of code so I am going to use snippets as much as possible to keep it clear. (If you need to see more code feel free to ask)
Here's my view widget:
class view extends StatefulWidget{
view({
Key key,
this.count = 0,
}) : super(key: key);
int count;
String _action='';
var _actionParams='';
var _data;
Function(String) callback;
void setAction(String newAction){
_action = newAction;
}
void setActionParams(String params){
_actionParams = jsonDecode(params);
}
void setData(String data){
_data = jsonDecode(data);
}
void incrementCounter(){
count++;
}
#override
_viewState createState() => _viewState();
}
class _viewState extends State<view>{
Object redrawObject = Object();
#override
Widget build(BuildContext context) {
/*
switch(widget._action){
case '':
break;
default:
return null;
}
*/
return Text("Counter: "+widget.count.toString());
}
#override
void initState(){
this.redrawObject = widget.key;
super.initState();
}
}
You can see in the commented code that I am planning to change the way the view builds itself in function of the data that gets passed to it.
What I have tried so far is to pass a ValueKey/ObjectKey to the view from main.dart in a constructor and then changing the object at runtime. Unfortunately that did not work.
At the top of my main.dart(accessible from anywhere within main) I have this:
Object redraw = Object();
final dataView = new view(key: ObjectKey(redraw));
Then in the body of the homepage I have the view and a floating button right under.
If I press the button it should increment the counter inside the view and force it to redraw. Here's the code I have tried so far:
body: Center(
child: dataView
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.badge),
onPressed: (){
dataView.incrementCounter();
redraw = new Object();
},
),
From what I understand, if the object that was used as a key gets changed, then flutter should rebuild the state for the widget. So I'm setting my object to a new object but it's not working.
I also tried something like this:
onPressed: (){
setState((){
dataView.incrementCounter();
redraw = new Object();
});
},
Eventually I'd like to use a navigator in conjunction with my view widget (so that we have a back button) but I don't know if this is possible.
It feels a bit like I'm fighting with the framework. Is there a different paradigm I should use (like pages?) or is it possible for me to do it this way?
How do I force my view widget to get redrawn?
Using Göktuğ Vatandaş' answer and GlobalKeys I was able to figure it out.
I made a reDraw() function inside the state and then I called it from my main using a GlobalKey.
Note: Wrapping in a container and using a key for the container is not necessary. Calling setState() is enough to force a redraw.
Here's the new view widget:
import 'dart:convert';
import 'package:flutter/cupertino.dart';
GlobalKey<_viewState> viewKey = GlobalKey();
class view extends StatefulWidget{
view({
Key key,
this.count = 0,
}) : super(key: key);
int count;
String _action='';
var _actionParams='';
var _data;
Function(String) callback;
void setAction(String newAction){
_action = newAction;
}
void setActionParams(String params){
_actionParams = jsonDecode(params);
}
void setData(String data){
_data = jsonDecode(data);
}
void incrementCounter(){
count++;
}
#override
_viewState createState() => _viewState();
}
class _viewState extends State<view>{
#override
Widget build(BuildContext context) {
/*
switch(widget._action){
case '':
break;
default:
return null;
}
*/
return Text("Counter: "+widget.count.toString());
}
#override
void initState(){
super.initState();
}
void reDraw(){
setState((){});
}
}
Here's where I declare the view widget in my main:
final dataView = new view(key: viewKey);
Here's where I call the reDraw() function:
floatingActionButton: FloatingActionButton(
child: Icon(Icons.badge),
onPressed: (){
dataView.incrementCounter();
viewKey.currentState.reDraw();
},
),
Thanks Göktuğ Vatandaş!
You can check flutter_phoenix's logic for redraw effect. I think its very useful or you can just use package itself. Basically it does what you trying to achive.
It creates a unique key in state.
Key _key = UniqueKey();
Injects it to a container.
#override
Widget build(BuildContext context) {
return Container(
key: _key,
child: widget.child,
);
}
And when you call rebirth it just refresh key and that causes view to rebuild.
void restartApp() {
setState(() {
_key = UniqueKey();
});
}

how to reuse previous state fields in flutter bloc pattern?

have the following. using sliding_up_panel that has body with messages and with a different view in panel content that pops up on click action from bottom bar.
#override
Stream<AppState> mapEventToState(
AppEvent event,
) async* {
if (event is LoadChat) {
List<Msg> msgs = await Api.getMessages();
yield LoadedChat(messages: msgs);
} else if (event is OrderPanelOpen) {
yield OpenedPanelState();
} else if (event is OrderPanelClose) {
yield ClosedPanelState();
}
}
Goal is to hide the appBar when panel is opened. appBar is present in AppLayout which is parent holding the SlidingUpPanel widget itself in a Scaffold.
class _AppLayoutState extends State<AppLayout> {
#override
Widget build(BuildContext context) {
var bloc = BlocProvider.of<AppBloc>(context);
return Container(
child: Scaffold(
appBar: widget.showAppBar
? AppBar(...)
: null,
bottomNavigationBar: BottomAppBar(...),
body: SlidingUpPanel(...),
),
);
}
}
following is the action that adds panel events to bloc
IconButton(
icon: Icon(Icons.description),
onPressed: () {
if (widget.pannelCtrl.isPanelClosed()) {
widget.pannelCtrl.open();
bloc.add(OrderPanelOpen());
} else {
widget.pannelCtrl.close();
bloc.add(OrderPanelClose());
}
})
problem here is SlidingUpPanel has a body that needs to show messages regardless of panel open or close. If panel open and close events are mapped to states with bloc, these open and close events has to be separate states but messages from current state has to be passed to new state by either as constructor params to new state or other ways. is that right approach to achieve this or is there anything else cleaner that I'm missing here.
class ClosedPanelState implements LoadedChat {
final messagesArg;
ClosedPanelState({this.messagesArg});
#override
Widget get currentView => Chat(messages: this.messagesArg);
#override
List<Object> get props => [];
#override
bool get showAppBar => true;
#override
String get title => 'Order Food';
#override
List<Msg> get messages => messages;
}
According to your question's title there's a thing you can do.
For your bloc class you can create a stack-like variable (you can use your own implementation of a LIFO stack or use a List, as you wish). Your bloc class must be Singleton in order to save the Events.
static final List<AppEvent> _events = new List<AppEvent>();
And on your _mapEventToState implementation add the events called to the variable:
#override
Stream<AppState> mapEventToState(
AppEvent event,
) async* {
_events.add(event); // New Line
if (event is LoadChat) {
List<Msg> msgs = await Api.getMessages();
yield LoadedChat(messages: msgs);
} else if (event is OrderPanelOpen) {
yield OpenedPanelState();
} else if (event is OrderPanelClose) {
yield ClosedPanelState();
}
}
Later you write a method for calling the last Event on the Stack/List.
dispatchPreviousState() {
this.add(_events.removeLast());
}
This worked for me to get the latest Event called and dispatching it again.
You can use reversible_bloc to add this behaviour to your bloc / cubit.
Ex.:
class MyReversibleCubit extends Cubit<int> with ReversibleBlocMixin {
MyReversibleCubit(int initialState) : super(initialState);
void changeValue(int newValue) => emit(newValue);
}
Then, when you need to rollback, use revert()
final myReversibleCubit = context.read<MyReversibleCubit>();
myReversibleCubit.changeValue(2);
myReversibleCubit.changeValue(4);
// Current state is 4
myReversibleCubit.revert();
// Current state back to 2

Flutter custom navigation highlight selected page

I'm currently building a custom bottom bar for quick navigation.
I used the Navigation Service described in this article
Now I want to add highlighting based on which page the user has selected.
I tried to add RouteAware to my BottomNav widget to update the menu when the routing changed but I'm not receiving any events only when starting my app.
class _BottomNavState extends State<BottomNav> with RouteAware {
String _selectedRoute;
AppRouteObserver _routeObserver;
#override
void initState() {
super.initState();
_routeObserver = AppRouteObserver();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_routeObserver.subscribe(this, ModalRoute.of(context));
}
#override
void dispose() {
_routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPush() {
print('didPush');
}
The Route observer is a simple class:
class AppRouteObserver extends RouteObserver<PageRoute> {
factory AppRouteObserver() => _instance;
AppRouteObserver._private();
static final AppRouteObserver _instance = AppRouteObserver._private();
}
I'm guessing that it has to with me not using the Navigator.pushNamed but the direct implementation of the Navigation Service.
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName, {var content}) {
return navigatorKey.currentState.pushNamed(routeName, arguments: content);
}
bool goBack() {
return navigatorKey.currentState.pop();
}
}
The reason I created the NavigationService is because I want to show a consistent layout on every page (menubar / bottom bar / background).
Is there a better way to solve this problem?
I fixed the issue by extending the NavigationService with a ChangeNotifier.
Now when the user clicks the button I call a setSelected function and notify the menu items to redraw them self's.
setSelected(String newRoute) {
_showLeading = !checkIsHome(newRoute);
this._currentRoute = newRoute;
notifyListeners();
}

How to add a click listener to an InputElement in GWT?

I've created the following checkbox
import com.google.gwt.dom.client.InputElement;
...
final InputElement bulkEditCheckbox = Document.get().createCheckInputElement();
Tried many formulas, but just can't wrap it or add a listener to it. Any suggestions?
PS: getting the following error when trying to wrap with SimpleCheckBox.wrap():
‘A widget that has an existing parent widget may not be added to the detach list’
This fixed my issue:
DOM.sinkEvents(element, Event.ONCLICK);
DOM.setEventListener(element, new EventListener() {
#Override
public void onBrowserEvent(Event event) {
if (Event.ONCLICK == event.getTypeInt()) {
...
}
}
});

GWT widget with JSNI not attached yet

I created a widget that extends SimplePanel and execute some JSNI in onLoad() method:
public class AceEditor extends SimplePanel implements HasText {
private JavaScriptObject editor;
#Override
protected void onLoad() {
super.onLoad();
editor = createEditor(getElement());
}
private static native JavaScriptObject createEditor(Element element) /*-{
return $wnd.ace.edit(element);
}-*/;
#Override
public native void setText(String value) /*-{
this.#org.obiba.opal.web.gwt.ace.client.AceEditor::editor.setValue(value);
}-*/;
#Override
public final native String getText() /*-{
return this.#org.obiba.opal.web.gwt.ace.client.AceEditor::editor.getValue();
}-*/;
}
The problem is when I call setText for the first time, the widget is not attached yet (onLoad was not called yet).
I use this widget with UiBinder.
I could use addAttachHandler and check if the widget is attached but it does not seem the right way to do it...
Any idea of why the widget is not attached sooner?
Can you put the call to createEditor in the constructor instead of onload ?
It seems that your code is calling the setText before the page is loaded.
Now if that is the case calling createEditor in the constructor should be proper.
Any idea of why the widget is not attached sooner?
By definition, it shouldn't be 'attached' until after you've added it to an already-attached parent (either to RootPanel, or another thing already added to RootPanel). Have you done so and onLoad still isn't called?
If your JS tool (in this case ace) requires that a DOM node exists, then you don't need to wait for onLoad to be called to do anything - as of your Widget subclass's constructor being finished, you must already have specified an element.
If you actually require that this element is attached to the document before you can perform operations on it, then yes, of course, you need to wait until onLoad is called.