Over a million developers have joined DZone.

Remember the State

· Java Zone

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

As a rule of thumb, your application should try to remember the state across sessions. So when a user hits the close button and then opens it after few mins/days, it should present exactly in the same way where he left. In this tip, I'm going to explain few things of how this could be achieved.

The first and foremost thing is the number of windows opened and current perspective and its state in each window. If you are writing a plugin for the IDE, you need not worry about this. Eclipse does that for you. If you are an RCP app, you have to do it yourself. You have to do it in the WorkbenchAdvisor.initialize() method:

public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor {

@Override
public void initialize(IWorkbenchConfigurer configurer) {

super.initialize(configurer);
configurer.setSaveAndRestore(true);
}

// other methods ...
}

Now this will take care of lot of things - the number of windows that are opened; location & size of those windows; perspective of each window; view & editors in each perspective; their locations,... All by setting that one boolean variable!

Now that our views and editors are restored by Eclipse, we need to ensure the state of them. Most of the views will be tree/table based. In these cases, the current selection is an important one to restore.

To store these items and anyother stuff you want to, Eclipse provides IMemento. An instance of this IMemento is passed to the view when its initialized and when workbench is closed. The information can be stored hierarchically with string keys and it will be persisted as an XML. If you are wondering why can't it be as simple as passing a Serializable Java object and the workbench persisting it, the answer is simple. The same class may not be there when Eclipse starts next time or even if the same class is available, it might have changed. IMemento avoids this problem by persisting the stae as an XML string.

So how do we use it? In the view, you have to override the init() and saveState() methods:

@Override
public void init(IViewSite site, IMemento memento) throws PartInitException {
this.memento = memento;
super.init(site, memento);
}

public void createPartControl(Composite parent) {
// create the viewer
restoreSelection();
}

private void restoreSelection() {
if (memento != null) {
IMemento storedSelection = memento.getChild("StoredSelection");
if (storedSelection != null) {
// create a structured selection from it
viewer.setSelection(selection);
}
}
}

@Override
public void saveState(IMemento memento) {
IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
if (!selection.isEmpty()) {
IMemento storedSelection = memento.createChild("StoredSelection");
// store the selection under storedSelection
}
}

Not just the selection, we can store any other information (which of the tree nodes are expanded, sorter & filter settings etc) which will be useful to restore the state.

Great. Now moving on from the views, the next item would be dialogs. Similar to the workbench windows, you can store the size, location and other elements of a dialog as well. The functionality for size and location is available by default, but you need to enable it by overriding the Dialog.getDialogBoundsSettings() method. The Dialog class stores & retrieves the size and location from IDialogSettings returned from that method. The original implementation returns null, so nothing is saved. We need to create an instanceof IDialogSettings and return it. Your plugin's Activator simplifies that. When required, it creates a dialog_settings.xml under your plugin's data area and store the dialog settings of all the dialogs. You have to create a separate section for each dialog.

private static final String MY_DIALOG_SETTINGS = "MY_DIALOG_SETTINGS";

@Override
protected IDialogSettings getDialogBoundsSettings() {
IDialogSettings settings = Activator.getDefault().getDialogSettings();
IDialogSettings section = settings.getSection(MY_DIALOG_SETTINGS);
if (section == null) {
section = settings.addNewSection(MY_DIALOG_SETTINGS);
}
return section;
}

In case you want to store only the location or size, you can specify it by overriding the getDialogBoundsStrategy() method.

Much like the IMemento, the IDialogSettings basically organizes the key-value strings in an hierarchical way. So along with the size & location, you can store any other information in this IDialogSettings. Its a good practice to store the values of the widgets (which radion button is selecte, checked state of a check box, etc) in the dialog, so its faster for an user who frequently repeats an operation.

Talking about the widgets, the trickier one is Combo. When the list of options are predefined and the user can't enter a new value, then its easy. But in places where the user can enter the values, (like File/Directory selection, search boxes), remembering them is not straight forward.

We shouldn't be storing every single value the user enters, but only the recently used ones. Probably with a limit of 5 or 10 items only. This can be done with the help of LinkedHashSet. It guarantees the order of the element, so whenever the user enters a new values, put the current value first in the set then add the rest of the elements (even if the first element is repeated, it won't change the position). Then take the first N elements and store it.

 

private static final String COMBO_STATE="COMBO_STATE";

private static final int HISTORY_SIZE = 5;

private String []comboState;

private void restoreComboState(IDialogSettings settings) {
comboState = settings.getArray(COMBO_STATE);
if(comboState ==null)
comboState = new String[0];
for (String value : comboState) {
myCombo.add(value);
}
}

private void saveComboState(IDialogSettings settings) {

// use LinkedHashSet to have the recently entered ones
LinkedHashSet<String> newState = new LinkedHashSet<String>();
newState.add(myCombo.getText());
newState.addAll(Arrays.asList(comboState));

// now convert it to the array of required size
int size = Math.min(HISTORY_SIZE, newState.size());
String[] newStateArray = new String[size];
newState.toArray(newStateArray);

// store
settings.put(COMBO_STATE, newStateArray);
}

One last piece. Look at this dialog:



Sometimes only for the first time you want to ask the question to the user. Thereafter if the user prefers, you can use the same answer. To simplify this, you can use the MessageDialogWithToggle. You need to pass the preference store and the key for the preference value. When the user selects the check box, the value will be stored. From the next time onwards, you can check the value from the preference and use it.

 

MessageDialogWithToggle.openYesNoQuestion(shell, "Remember me", "Is this tip useful?", 
"Don't bug me with this question again", true,
Activator.getDefault().getPreferenceStore(), "BUG_USER_KEY");

There you go:


From http://blog.eclipse-tips.com

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}