I'm writing a project in GWT over GAE with SmartGWT.
I've got a DB with object, each having a "father" object and "sons", and I'm using a TreeGrid to represent them. I already have a GWT-RPC service that gets the sons of a given node.
What I need now is to somehow extend the DataSource class s.t when a tree node is opened, I will be able to use my own service to go and fetch it's sons - and then return them as something the TreeGrid can work with.
I know I'm suppose to override transformRequest and transformResponse, but I have no idea how. Any code sample / explanation will be greatly appreciated!
This is what I have so far - not sure it's even remotely correct:
budgetTree.setDataSource(new DataSource() {
#Override
protected Object transformRequest(final DSRequest dsRequest) {
expensesService.getExpensesByYear(2008,
new AsyncCallback<ExpenseRecord[]>() {
#Override
public void onSuccess(ExpenseRecord[] result) {
System.out.println("Returned " + result.length + " expense record ");
dsRequest.setAttribute("dsResult", result);
}
#Override
public void onFailure(Throwable caught) {
System.out.println("Failed to run query");
}
});
return dsRequest;
}
#Override
protected void transformResponse(DSResponse response, DSRequest request,
Object data) {
Record[] recs = request.getAttributeAsRecordArray("dsResult");
response.setData(recs);
super.transformResponse(response, request, data);
}
});
Since you are performing the actual request yourself , you first need to look at
setDataProtocol(DSProtocol.CLIENTCUSTOM);
Then in both onSuccess and onFailure you would need to call processResponse() which will call transformResponse()
public class MyDatasource extends DataSource{
public MyDatasource(){
setDataProtocol(DSProtocol.CLIENTCUSTOM)
}
#Override
protected Object transformRequest(final DSRequest dsRequest) {
expensesService.getExpensesByYear(2008,
new AsyncCallback<ExpenseRecord[]>() {
#Override
public void onSuccess(ExpenseRecord[] result) {
DSResponse response = new DSResponse();
System.out.println("Returned " + result.length + " expense record ");
dsRequest.setAttribute("dsResult", result);
Record[] recs = request.getAttributeAsRecordArray("dsResult");
response.setData(recs);
processResponse(dsRequest.getRequestId(), dsResponse);
}
#Override
public void onFailure(Throwable caught) {
DSResponse response = new DSResponse();
System.out.println("Failed to run query");
processResponse(dsRequest.getRequestId(), dsResponse);
}
});
return dsRequest;
}
}
Related
I started to learn about the paging library, And I've a problem.
I'm able to fetch the data and show inside my recyclerView, But I've really weird beahviors.
I Put logs on loadInitial, loadBefore and loadAfter and the first time loadInitial and loadAfter call one after another immediatly.
When I scroll down, I log getPage from the response and it give me the right page number after 20 item, but I really suspect it just load ALL the pages for the first time, I mean, I can literly scroll 500 item without wait to load even one time.
The first problem as I said - it called loadInitial and loadAfter one after another immediatly at the first time.
second problem - when I scroll up, loadBefore NEVER triggered.
I don't sure which code I should share, but I suspect the problem is somewhere inside the data source, If you need more let me know in the comments
CODE:
public class MoviesDataSource extends PageKeyedDataSource<Integer, Results> {
private static final int FIRST_PAGE = 1;
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, Results> callback) {
Log.i(TAG, "loadInitial: ");
ApiService.getAllMovies().getAllMovies(API_KEY, FIRST_PAGE).enqueue(
new Callback<AllMovies>() {
#Override
public void onResponse(Call<AllMovies> call, Response<AllMovies> response) {
if (response.body() != null) {
Log.i(TAG, "onResponse: " + response.body().getPage());
List<Results> results = Arrays.asList(response.body().getResults());
callback.onResult(results, null, FIRST_PAGE + 1 );
}
}
#Override
public void onFailure(Call<AllMovies> call, Throwable t) {
}
}
);
}
#Override
public void loadBefore(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, Results> callback) {
Log.i(TAG, "loadBefore: ");
ApiService.getAllMovies().getAllMovies(API_KEY, params.key).enqueue(
new Callback<AllMovies>() {
#Override
public void onResponse(Call<AllMovies> call, Response<AllMovies> response) {
Log.i(TAG, "onResponse: " + response.body().getPage());
Integer key = (params.key > 1) ? params.key -1 : null;
if (response.body() != null) {
List<Results> results = Arrays.asList(response.body().getResults());
callback.onResult(results, key );
}
}
#Override
public void onFailure(Call<AllMovies> call, Throwable t) {
}
}
);
}
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, Results> callback) {
Log.i(TAG, "loadAfter: ");
ApiService.getAllMovies().getAllMovies(API_KEY, params.key).enqueue(
new Callback<AllMovies>() {
#Override
public void onResponse(Call<AllMovies> call, Response<AllMovies> response) {
Log.i(TAG, "onResponse: " + response.body().getPage());
Integer key = params.key + 1; // calculate until there is no data
if (response.body() != null) {
List<Results> results = Arrays.asList(response.body().getResults());
callback.onResult(results, key );
}
}
#Override
public void onFailure(Call<AllMovies> call, Throwable t) {
}
}
);
}
}
MainActivity
#Override
public void onChanged(PagedList<Results> results) {
adapter.submitList(results);
}
ViewModel
First problem: loadAfter gets called based on PagedList.Config prefetch distance and page size configuration.To control how and when a PagedList queries data from its DataSource, see PagedList.Config
second: you load data in one direction so loadBefore never gets called.
Also, you should be using synchronous retrofit calls, you can find reference here: network only paging
Im developing app based on same api while learning about jetpack stuff, am little ahead of you (caching db + network) :)
View model has been initialized by the following code inside fragment.
viewModel.getContacts(pageNumber, AppConstants.DIRECTION).observe(getActivity(), list -> {
adapter.submitList(list);
});
where viewModel.getContacts() method calls a repository method which in turn makes the web request and brings the response back.
public MutableLiveData<List<Contact>> getAllContacts(int page, String sortedBy) {
return repository.getAllContacts(page, sortedBy);
}
where repository.getAllContacts() method is
public MutableLiveData<List<Contact>> getAllContacts(int page, String orderBy) {
if (allContacts == null) {
allContacts = new MutableLiveData<>();
}
//we will load it asynchronously from server in this method
loadContacts(page, orderBy);
return allContacts;
}
private void loadContacts(int page, String orderBy) {
Call<ContactsResponse> call = bearerApiInterface.getContacts(page, orderBy);
call.enqueue(new Callback<ContactsResponse>() {
#Override
public void onResponse(Call<ContactsResponse> call, Response<ContactsResponse> response) {
Timber.e("Contacts Response => " + new GsonBuilder().setPrettyPrinting().create().toJson(response.body()));
//finally we are setting the list to our MutableLiveData
allContacts.setValue(response.body().getResult().getData());
}
#Override
public void onFailure(Call<ContactsResponse> call, Throwable t) {
}
});
}
And here is my recycler view scroll listener
recyclerView.setOnScrollListener(new EndlessRecyclerOnScrollListener(linearLayoutManager) {
#Override
public void onLoadMore(int current_page) {
loadNextPage();
}
});
Upon scrolling when loadNextPage() gets called, how viewModel.getContacts() could be triggered from loadNextPage() method.
What are the options to send the call again with incremented page number and observe it with same viewModel.getContacts() method. Paging list adapter is not an option for now as the response needs to be updated, deleted & customized while paging list adapter isn't doing that without datasource and snapshot inclusion which isn't working (any help with that would be very helpful if it is possible).
And below is the code for deleting any item from recycler view.
#Override
public void onItemDelete(RecyclerView.ViewHolder viewHolder, int position) {
mActivity.showProgressBar(true);
Timber.e("Delete the contact at position " + position);
viewModel.deleteContact(adapter.getContactAt(viewHolder.getAdapterPosition()).getId(), adapter.getContactAt(viewHolder.getAdapterPosition())).observe(this, new Observer<Boolean>() {
#Override
public void onChanged(Boolean isSuccess) {
if (isSuccess) {
mActivity.showErrorDialog("Contact Deleted Successfully", null, null);
listAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
} else {
mActivity.showErrorDialog("Something went wrong, please try again", null, null);
}
}
});
}
The view model delete method is
public MutableLiveData<Boolean> deleteContact(int id, Contact contact) {
return repository.deleteThisContact(id, contact);
}
And the repository delete method is
public MutableLiveData<Boolean> deleteThisContact(int contactId, Contact contact) {
if (deleteContact == null)
deleteContact = new MutableLiveData<>();
callDeleteContact(contactId, contact);
return deleteContact;
}
private void callDeleteContact(int contactId, Contact contact) {
Call<JsonObject> call = bearerApiInterface.deleteContact(contactId);
call.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.isSuccessful() && response.code() == 200) {
Timber.e("***** Contact Deleted Successfully => " + new GsonBuilder().setPrettyPrinting().create().toJson(response.body()));
delete(contact);
deleteContact.setValue(true);
} else {
try {
deleteContact.setValue(false);
String errorMessage = new APIError().extractMessage(new JSONObject(response.errorBody() != null ? response.errorBody().string().trim() : null));
Timber.e("***** Error message is => " + errorMessage);
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
deleteContact.setValue(false);
Timber.e("***** onFailure" + "response: " + t.getMessage());
}
});
}
Any related code which might be worthy of sharing can be asked. Skipped for simplicity.
You will need to implement the android paging:
First, you have to add on gradle the paging lib:
implementation 'androidx.paging:paging-runtime:2.1.0'
Your data source must extend the PageKeyedDataSource, so, you have to implement 3 methods, loadInitial, loadAfter and loadBefore
On your view model you must create a pager config variable, like:
private val config: PagedList.Config = PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.setInitialLoadSizeHint(PAGE_SIZE_HINT)
.setEnablePlaceholders(false)
.build()
It will set up how the pager must be executed, and do you have to create an executor to load the data:
private val executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE)
And after all, create a livedata to receive the list:
val your_source: LiveData<PagedList<YourSource>> = LivePagedListBuilder(dataFactory, config)
.setFetchExecutor(executor)
.build()
Your recycler view adapter must be changed to a PagedListAdapter instead.
I recommend this article:
https://androidwave.com/pagination-in-recyclerview/
i have below code that gets executed when an admin is creating or deleting a user in the keycloak UI.
Through the help of the adminEvent: http://www.keycloak.org/docs/3.0/server_admin/topics/events/admin.html
Creating a user returns the user details via adminEvent.getRepresentation().
However when deleting a user returns me a null.
This is also the same when deleting a role, deleting a group or deleting a user_session.(ResourceTypes)
My question is how can i retrieve the deleted details?
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.UserModel;
public void handleResourceOperation(AdminEvent adminEvent, UserModel user) {
MQMessage queueMessage = new MQMessage();
queueMessage.setIpAddress(adminEvent.getAuthDetails().getIpAddress());
queueMessage.setUsername(user.getUsername());
switch (adminEvent.getOperationType()) {
case CREATE:
LOGGER.info("OPERATION : CREATE USER");
LOGGER.info("USER Representation : " + adminEvent.getRepresentation());
String[] split = adminEvent.getRepresentation().split(",");
queueMessage.setTransactionDetail("Created user " + split[0].substring(12));
sendQueueMessage(adminEvent, queueMessage);
break;
case DELETE:
LOGGER.info("OPERATION : DELETE USER");
LOGGER.info("USER Representation : " + adminEvent.getRepresentation());
queueMessage.setTransactionDetail("User has been deleted.");
sendQueueMessage(adminEvent, queueMessage);
break;
}
I'm not sure you got the answer by now. Sharing the solution that may be helpful for others. User details can be captured in postInit method of EventListenerProviderFactory as below,
public class UserEventListenerProviderFactory implements EventListenerProviderFactory {
#Override
public EventListenerProvider create(KeycloakSession keycloakSession) {
return new UserEventListenerProvider(keycloakSession);
}
#Override
public void init(Config.Scope scope) {
}
#Override
public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
keycloakSessionFactory.register(
(event) -> {
if (event instanceof UserModel.UserRemovedEvent) {
UserModel.UserRemovedEvent dEvent = (UserModel.UserRemovedEvent) event;
//TODO YOUR LOGIC WITH `dEvent.getUser()`
}
});
}
#Override
public void close() {
}
#Override
public String getId() {
return "sample_event_listener";
}
}
I want to implement a very simple Java Telegram Client, which is capable of sending and receiving messages and store the sessions across multiple starts. I already managed to authenticate and receive messages
api = new TelegramApi(apiState, new AppInfo(API_ID, "console", "1", "1", "en"), new ApiCallback() {
#Override
public void onAuthCancelled(TelegramApi api) {
Log.d(TAG, "-----------------CANCELLED----------------");
Log.d(TAG, api.getApiContext().toString());
}
#Override
public void onUpdatesInvalidated(TelegramApi api) {
Log.d(TAG, "-----------------INVALIDATED----------------");
Log.d(TAG, api.getApiContext().toString());
}
#Override
public void onUpdate(TLAbsUpdates tlAbsUpdates) {
Log.d(TAG, "-----------------UPDATE----------------");
Log.d(TAG, tlAbsUpdates.toString());
if (tlAbsUpdates instanceof TLUpdateShortMessage) {
Log.d(TAG, "-----------------UPDATE CHAT MESSAGE----------------");
int senderId = ((TLUpdateShortMessage) tlAbsUpdates).getUserId();
Log.d(TAG, "Message from " + senderId);
String message = ((TLUpdateShortMessage) tlAbsUpdates).getMessage();
Log.d(TAG, message);
activity.appendMessage(TAG, message);
}
}
});
api.switchToDc(2);
TLConfig config = null;
try {
config = api.doRpcCallNonAuth(new TLRequestHelpGetConfig());
} catch (TimeoutException | IOException e) {
e.printStackTrace();
}
apiState.updateSettings(config);
However, I struggle to send messages to another user. For the beginning, it would be enough if I could send a message back to the user, who sent me a message before (by retrieving the senderId, as you can see in the onUpdate method before). However, if someone could also help me with retrieving the ids of my saved contacts, it would be perfect.
Furthermore, I want to store the sessions accross multiple startups, since I get a FLOOD_WAIT error (420), if I test my code to often.
For this I used https://github.com/rubenlagus/TelegramApi/blob/51713e9b6eb9e0ae0d4bbbe3d4deffff9b7f01e4/src/main/java/org/telegram/bot/kernel/engine/MemoryApiState.java and its used classes (e.g. TLPersistence), which stores and loads the ApiState. However, apparently it does not store the signin status, since I always have to authenticate my number every time I update the code.
By the way, I am using Api layer 66 (https://github.com/rubenlagus/TelegramApi/releases).
UPDATE 1:
Problems with sending messages solved myself:
private void sendMessageToUser(int userId, String message) {
TLInputPeerUser peer = new TLInputPeerUser();
peer.setUserId(userId);
TLRequestMessagesSendMessage messageRequest = new TLRequestMessagesSendMessage();
messageRequest.setFlags(0);
messageRequest.setPeer(peer);
messageRequest.setRandomId(new SecureRandom().nextLong());
messageRequest.setMessage(message);
api.doRpcCallNonAuth(messageRequest, 1500, new RpcCallback<TLAbsUpdates>() {
#Override
public void onResult(TLAbsUpdates tlAbsUpdates) {
Log.d(TAG, "-----------------------MESSAGE SENT-----------------------");
}
#Override
public void onError(int i, String s) {
Log.d(TAG, "-----------------------MESSAGE SENT ERROR-----------------------");
Log.d(TAG, String.valueOf(i));
if(s != null) {
Log.d(TAG, s);
}
}
});
}
However, now I am stuck at finding the userIds of my contacts.
After first update this is left:
Saving the session state (and signin state)
Find userIds of contacts
Update 2:
I managed to fetch the users, with which there are already dialogs. This is enough for my use case, however, loading all contacts would be perfect. This is how to load users from existing dialogs:
private int getUserId(String phone) throws InterruptedException {
TLRequestMessagesGetDialogs dialogs = new TLRequestMessagesGetDialogs();
dialogs.setOffsetId(0);
dialogs.setLimit(20);
dialogs.setOffsetPeer(new TLInputPeerUser());
CountDownLatch latch = new CountDownLatch(1);
api.doRpcCallNonAuth(dialogs, 1500, new RpcCallback<TLAbsDialogs>() {
#Override
public void onResult(TLAbsDialogs tlAbsDialogs) {
Log.d(TAG, "----------------------getUsers--------------------");
for(TLAbsUser absUser : ((TLDialogs) tlAbsDialogs).getUsers()) {
users.add((TLUser) absUser);
}
latch.countDown();
}
#Override
public void onError(int i, String s) {
Log.d(TAG, "----------------------getUsers ERROR--------------------");
latch.countDown();
}
});
latch.await();
for(TLUser user : users) {
if(user.getPhone().equals(phone)) {
return user.getId();
}
}
return 0;
}
After second update this is left:
Saving the session state (and signin state)
Get user ids from contacts instead of dialogs
I would like to add a GWT autosuggest textbox in JSP.
Could someone provide some insight into this?
Thanks
Typically GWT is considered a web application framework which is different to a widget framework. Personally I would consider GWT too heavy to just add an autosuggest to a simple web page and instead use something like jQuery autocomplete.
Having said that, there's nothing magical about running GWT code. Follow GWT standard module layout and just set up your JSP-page as a GWT host page where you alter the paths to be absolute to your compiled module.
Here an example of how I was able to get a suggest box to work. I make an RPC call to the database while the user is typing.
I agree that you could do something similar in jQuery but why would you when GWT has the widget available?
Hope this helps!
vendorSuggestBox = new SuggestBox(new SuggestionOracle()); //client package
public class SuggestionOracle extends SuggestOracle { //shared package
public boolean isDisplayStringHTML() {
return true;
}
#SuppressWarnings("unchecked")
public void requestSuggestions(Request request, Callback callback) {
ItemMovementRemoteServiceAsync service=GWT.create(ItemMovementRemoteService.class);
service.getVendors(request, new SuggestionCallback(request,callback));
}
#SuppressWarnings("unchecked")
class SuggestionCallback implements AsyncCallback {
private SuggestOracle.Request req;
private SuggestOracle.Callback callback;
public SuggestionCallback(SuggestOracle.Request _req, SuggestOracle.Callback _callback) {
req=_req;
callback=_callback;
}
public void onFailure(Throwable caught) {
callback.onSuggestionsReady(req, new SuggestOracle.Response());
}
public void onSuccess(Object result) {
callback.onSuggestionsReady(req, (SuggestOracle.Response) result);
}
}
public SuggestOracle.Response getVendors(Request req) { //server package
Connection db=null;
PreparedStatement ps=null;
ResultSet rs=null;
SuggestOracle.Response resp = new SuggestOracle.Response();
List<Suggestion> suggestions=new ArrayList<Suggestion>();
int count=0;
try {
db=Database.open("ACM0");
ps=db.prepareStatement(
" SELECT VE_CD,upper(VE_NAME) VE_NAME" +
" FROM AP.VE_WEB " +
" WHERE (VE_NAME NOT LIKE 'AC Moore%') " +
" AND (lower(VE_NAME) LIKE ? OR VE_CD LIKE ?)" +
" ORDER BY VE_NAME");
ps.setString(1, "%"+req.getQuery().toLowerCase()+"%");
ps.setString(2, "%"+req.getQuery().toLowerCase()+"%");
rs=ps.executeQuery();
while(rs.next() && count < 25) {
suggestions.add(new ASuggestion(rs.getString("VE_NAME").trim()+"-"+rs.getString("VE_CD").trim()));
count++;
}
resp.setSuggestions(suggestions);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
Database.close(db);
}
return resp;
}
public class ASuggestion implements IsSerializable, Suggestion { //shared package model object
private String s;
public ASuggestion(){}
public ASuggestion(String s) {
this.s=s;
}
public String getDisplayString() {
return s;
}
public String getReplacementString() {
return s;
}