I am learning how to use WebSockets in Spring boot with an Android client. I picked up Scarlet by Tinder, it looks like a great library. My issue though how to call my websocket endpoint in scarlet. I already have a working sample spring boot project that constantly updates the locations of users, below is the project
Firstly we have our messagebroker WebSocketMessageBrokerConfigurer
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
}
}
Our listener that listens to connections/disconnections as well as page loads
@Component
public class WebSocketEventListener {
private static final Logger logger = LoggerFactory.getLogger(WebSocketEventListener.class);
@Autowired
private SimpMessageSendingOperations messagingTemplate;
//method called when user open page in browser
@EventListener
public void handleWebSocketConnectListener(SessionConnectedEvent event) {
logger.info("Received a new web socket connection");
}
//method called when user close page in browser
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
String username = (String) headerAccessor.getSessionAttributes().get("username");
if(username != null) {
logger.info("User Disconnected : " + username);
//remove user from latest Location Feed
CommunicationController.latestLocationFeed.remove(username);
//transmitting current user's latest location feed
messagingTemplate.convertAndSend("/app/getData", new LocationBean());
}
}
}
And lastly our controller that basically with 1 endpoint that saves and updates all users with new locations that are subscribed to /app/getData
public static Map<String, LocationBean> latestLocationFeed = new HashMap<String, LocationBean>();
@MessageMapping("/saveLocation") //for saving current user's location in memory
@SendTo("/app/getData") //for transmitting all connected user's latest location
public List<LocationBean> saveLocation(@Payload LocationBean bean, SimpMessageHeaderAccessor headerAccessor) {
//getting current user's location from web socket
headerAccessor.getSessionAttributes().put("username", bean.getUser());
logger.debug("\tUser:"+bean.getUser()+" >>> LocationBean:"+bean.toString());
//unique user wise latest location feed set in-memory hash map
latestLocationFeed.put(bean.getUser(), bean);
//converting map to list
Collection<LocationBean> values = latestLocationFeed.values();
ArrayList<LocationBean> listOfValues = new ArrayList<LocationBean>(values);
logger.info("\tPublicLocationFeed:"+listOfValues.toString());
//send all user's latest location feed
return listOfValues;
}
Using this on the web is very easy, ideally we have SockJS and Stomp,
We first initialize SockJS with our registered endpoint registry.addEndpoint("/ws").withSockJS() in spring boot like
var socket = new SockJS('/ws');
Then make an instance of StompClient from SockJS like
```stompClient = Stomp.over(socket);````
And finally connect
stompClient.connect({}, whenConnected, whenError);
Once we are connected we can then subscribe to our /app/getData which updates us with new user locations like
stompClient.subscribe('/app/getData', whenMessageReceived);
And in case we want to send our new location we can do this with the /app/saveLocation endpoint like
stompClient.send( "/app/saveLocation", {}, getLocationBean() );
And as simple as that we have the web working, my issue is how to do this exact process in Android using scarlet.
I cannot seem to even get the URL right, I always get a connection failed response.
My service looks like this
public interface GdaxService {
@Receive
Flowable<WebSocket.Event> observeWebSocketEvent();
@Send
void sendSubscribe(String message);
@Receive
Flowable<String> observeLocationChangedResponse();
}
And I implement it like
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10,TimeUnit.SECONDS)
.build();
GdaxService scarletGdaxService = new Scarlet.Builder()
//.lifecycle(this)
.webSocketFactory(OkHttpClientUtils.newWebSocketFactory(okHttpClient, "ws://localhost:8080/ws/app/getData"))
.addMessageAdapterFactory(new GsonMessageAdapter.Factory())
.addStreamAdapterFactory(new RxJava2StreamAdapterFactory())
.build()
.create(GdaxService.class);
scarletGdaxService.observeWebSocketEvent()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableSubscriber<WebSocket.Event>() {
@Override
public void onNext(WebSocket.Event event) {
Log.d(TAG, "Current socket connection status: "+event);
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onComplete() {
Log.d(TAG, "Connection status flowable complete");
}
});
scarletGdaxService.observeLocationChangedResponse()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableSubscriber<String>() {
@Override
public void onNext(String s) {
txtResponse.setText(s);
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onComplete() {
Log.d(TAG, "Flowable has completed");
}
});
Anyone with experience on how to replicate my web implementation in Android can help.