I have seen many questions like this, but still I can't understand what's going wrong with my implementation.
I'm trying to build a client-server application using RMI to implement the communications; it is supposed to become a game, but right now I'm focusing just on basic client interface operations like login, player list etc.
On the client side I have a swing GUI which displays, among other things, a list of the players currently registered on the server. Every time a new player register on the server, the server sends the full list of registered players to all registered clients. A client must then clear its player list content and fill it up again with the received values.
The list received is an array of IPlayer
, which is the remote interface exported by the Player
class. To update the JList
on the client I'm using the updatePlayerList()
method below:
class BrowseScreen extends Screen implements ActionListener, MouseListener, ListSelectionListener {
private JList<String> playerList;
...
@Override
protected void init() {
setLayout(new BorderLayout());
...
playerList = new JList<String>();
playerList.setModel(new DefaultListModel<String>());
JScrollPane playerScroll = new JScrollPane(playerList);
playerScroll.setBorder(BorderFactory.createTitledBorder(null, "Players", TitledBorder.CENTER, TitledBorder.DEFAULT_POSITION));
add(playerList, BorderLayout.EAST);
...
}
public void updatePlayerList(IPlayer[] list) {
try {
DefaultListModel<String> model = (DefaultListModel<String>) playerList.getModel();
model.clear();
for (IPlayer p : list) {
model.addElement(p.getName()); // remote method invocation
}
System.out.println(model); // added to check the content of the list model
} catch (RemoteException e) {
// TODO auto-generated catch block
e.printStackTrace();
}
}
...
}
(NOTE: Screen
is just a JPanel
extension I'm using to define the different screens I have in my application, like login, room list etc...)
When I try to open multiple clients what happens is that on random clients the list goes completely blank, and each time I login/out with one of the clients the JList
s update and some other (still random) client may end up with an empty list.
The list model should be correctly updated with the new data, as I'm seeing the correct output on the console.
I read some answers to the "JList
not updating" problem, and the most common answer was that the model update should be performed in the EDT thread. I'm not sure if I'm doing this correctly, but I have this main:
public static void main(String[] args) throws RemoteException {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
Client client = new Client();
GuiClient gui = new GuiClient(client);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
(NOTE: the GuiClient
object use the reference to Client
to perform complex operations which involve interaction with the server. All the user interface components, like the JFrame
where the previous Screen
is displayed, are managed by GuiClient
)
I hope I explained the situation well (I'm new to stackoverflow); my project has now many classes and I cannot give a full picture of it here.
Any help is very appreciated :)
EDIT: the problem was solved using a SwingWorker
to perform the RMI calls.
This is how I changed the updatePlayerList()
method:
public void updatePlayerList(IPlayer[] list) {
new ReadPlayerList(list, playerList).execute();
}
and this is the ReadPlayerList
class, which extends SwingWorker
:
public class ReadPlayerList extends SwingWorker<Void, String> {
private IPlayer[] list;
private JList<String> playerList;
private boolean listCleared;
public ReadPlayerList(IPlayer[] list, JList<String> playerList) {
this.list = list;
this.playerList = playerList;
listCleared = false;
}
@Override
protected Void doInBackground() throws Exception {
for (IPlayer p : list) {
publish(p.getName());
}
return null;
}
@Override
protected void process(List<String> chunks) {
DefaultListModel<String> model = (DefaultListModel<String>) playerList.getModel();
if (!listCleared) {
model.clear();
listCleared = true;
}
for (String name : chunks) {
model.addElement(name);
}
}
}
This solved the problem of the JList not displaying its content. Now the challange is to make sure that this update operation is always atomic. I guess I'll have to write some sort of "thread safe JList" extension, but this is another story :)
You've got a complex system that involved client-server communication, and you're seeing random unpredictable errors. In sum you're seeing all the classic symptoms of a threading problem.
Yes you're starting up your GUI on the Swing thread fine, but that's likely not where the problems coming from. In all likelihood (and I have to guess), it's because you're not handling threading correctly with the RMI communication. Are you using a SwingWorker to do this? I would if I were you, so that you could isolate the communications code in a thread background from the Swing event thread, and so you could easily have the communications code communicate with the Swing code on the EDT.
If you need more specific help, you'll need to show more, preferably by posting a program that is as close to a minimal example program as possible.