I'm developing a Flutter plugin to implements an iOS SDK and an Android SDK in Flutter. In both native SDKs, there is an object called Peripheral, which is a complexe object extending and implementing other objects. If I want to use theses Objects, do I have to implement them in Flutter too ? Or can I just create an manipulate instances of those objects from dart.
Right now, I'm trying to manipulate instances by have a PeripheralObject that calls a function in constructor that will create an instance in native Java (for Android) of a peripheral, place it in a hash map, and return it's memory adress to dart. In dart, I keep the memory adress of the Java object and when I call a function, like getName, I pass to the method channel the java memory adress and with that, I can retrieve from the map my instance of the native object, call the method and send back the answer. Is it a good way of resolving the problem or is there other better way to do so ?
Here is my dart object:
class Peripheral {
late String _objectReference;
late String _localName, _uuid;
Peripheral({required String localName, required String uuid}) {
_uuid = uuid;
_localName = localName;
_newPeripheralInstance(localName, uuid);
}
Future<void> _newPeripheralInstance(String localName, String uuid) async {
_objectReference = (await PeripheralPlatform.instance.newPeripheralInstance(localName, uuid))!;
return;
}
String get objectReference => _objectReference;
Future<String?> getModelName() async {
return PeripheralPlatform.instance.getModelName(_objectReference);
}
Future<String?> getUuid() async {
return PeripheralPlatform.instance.getUuid(_objectReference);
}
}
Here is my Dart Method Channel :
class MethodChannelPeripheral extends PeripheralPlatform {
/// The method channel used to interact with the native platform.
#visibleForTesting
final methodChannel = const MethodChannel('channel');
#override
Future<String?> newPeripheralInstance(String localName, String uuid) async {
String? instance = await methodChannel.invokeMethod<String>('Peripheral-newPeripheralInstance', <String, String>{
'localName': localName,
'uuid': uuid
});
return instance;
}
#override
Future<String?> getModelName(String peripheralReference) {
return methodChannel.invokeMethod<String>('Peripheral-getModelName', <String, String>{
'peripheralReference': peripheralReference
});
}
#override
Future<String?> getUuid(String peripheralReference) {
return methodChannel.invokeMethod<String>('Peripheral-getUuid', <String, String>{
'peripheralReference': peripheralReference
});
}
}
And here is my Android Java file :
public class PluginPeripheral {
private static Map<String, Peripheral> peripheralMap = new HashMap<>();
public static void handleMethodCall(String method, MethodCall call, MethodChannel.Result result) {
method = method.replace("Peripheral-", "");
switch (method) {
case "newPeripheralInstance":
newPeripheralInstance(call, result);
break;
case "getModelName":
getModelName(call, result);
break;
case "getUuid":
getUuid(call, result);
break;
default:
result.notImplemented();
break;
}
}
private static void newPeripheralInstance(MethodCall call, MethodChannel.Result result) {
if (call.hasArgument("uuid") && call.hasArgument("localName")) {
String uuid = call.argument("uuid");
String localName = call.argument("localName");
if (localName == null || uuid == null) {
result.error("Missing argument", "Missing argument 'uuid' or 'localName'", null);
return;
}
Peripheral peripheral = new Peripheral(localName, uuid);
peripheralMap.put(peripheral.toString(), peripheral);
result.success(peripheral.toString());
}
}
private static void getModelName(MethodCall call, MethodChannel.Result result) {
if (call.hasArgument("peripheralReference")) {
String peripheralString = call.argument("peripheralReference");
if (peripheralString == null) {
result.error("Missing argument", "Missing argument 'peripheral'", null);
return;
}
Peripheral peripheral = peripheralMap.get(peripheralString);
if (peripheral == null) {
result.error("Invalid peripheral", "Invalid peripheral", null);
return;
}
result.success(peripheral.getModelName());
} else {
result.error("Missing argument", "Missing argument 'peripheralReference'", null);
}
}
private static void getUuid(MethodCall call, MethodChannel.Result result) {
if (call.hasArgument("peripheralReference")) {
String peripheralString = call.argument("peripheralReference");
if (peripheralString == null) {
result.error("Missing argument", "Missing argument 'peripheral'", null);
return;
}
Peripheral peripheral = peripheralMap.get(peripheralString);
if (peripheral == null) {
result.error("Invalid peripheral", "Invalid peripheral", null);
return;
}
result.success(peripheral.getUuid());
} else {
result.error("Missing argument", "Missing argument 'peripheralReference'", null);
}
}
}
The alternative way is to convert an object to a map in Android and back to an object in Flutter. Something like this:
Flutter/Dart:
class Device {
String? id;
String? name;
...
Device.fromMap(Map<String, dynamic> map) {
id = map['id'];
name = map['name'];
}
}
final map = await methodChannel.invokeMethod('requestDevice');
final device = Device.fromMap(map.cast<String, dynamic>());
Android/Kotlin:
data class Device(
val id: String,
val name: String?
) {
...
fun toMap(): Map<String, Any?> {
return mapOf(
"id" to id,
"name" to name
)
}
}
override fun onMethodCall(#NonNull call: MethodCall, #NonNull result: Result) {
...
result.success(device.toMap())
...
}
Related
I am developing an Android security app and have decided to implement the PlayIntegrity API as an alternative to SafetyNet API. I have already completed the necessary setup steps such as enabling the Play and Cloud console, however, I am encountering an issue where I am getting an error 'GOOGLE SERVER UNAVAILABLE' when trying to obtain a token. Can anyone provide any insight into why this might be happening and possible solutions? Any help would be greatly appreciated.
Please see below code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// playIntegritySetup.lol();
getToken();
}
private void getToken() {
String nonce = Base64.encodeToString(generateNonce(50).getBytes(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
// Create an instance of a manager.
IntegrityManager integrityManager = IntegrityManagerFactory.create(getApplicationContext());
// Request the integrity token by providing a nonce.
Task<IntegrityTokenResponse> integrityTokenResponse = integrityManager.requestIntegrityToken(
IntegrityTokenRequest.builder()
.setNonce(nonce)
.build());
integrityTokenResponse.addOnSuccessListener(new OnSuccessListener<IntegrityTokenResponse>() {
#Override
public void onSuccess(IntegrityTokenResponse integrityTokenResponse) {
String integrityToken = integrityTokenResponse.token();
SplashActivity.this.doIntegrityCheck(integrityToken);
Log.e("Integrity Token", "integrity token from the app" + integrityToken);
}
});
integrityTokenResponse.addOnFailureListener(e -> showErrorDialog("Error getting token from Google. Google said: " + getErrorText(e)));
}
private void doIntegrityCheck(String token) {
AtomicBoolean hasError = new AtomicBoolean(false);
Observable.fromCallable(() -> {
OkHttpClient okHttpClient = new OkHttpClient();
Response response = okHttpClient.newCall(new Request.Builder().url("money control url" + "token from backend server" + token).build()).execute();
Log.e("Token", "token from the app" + token);
if (!response.isSuccessful()) {
hasError.set(true);
return "Api request error. Code: " + response.code();
}
ResponseBody responseBody = response.body();
if (responseBody == null) {
hasError.set(true);
return "Api request error. Empty response";
}
JSONObject responseJson = new JSONObject(responseBody.string());
if (responseJson.has("error")) {
hasError.set(true);
return "Api request error: " + responseJson.getString("error");
}
if (!responseJson.has("deviceIntegrity")) {
hasError.set(true);
}
return responseJson.getJSONObject("deviceIntegrity").toString();
}) // Execute in IO thread, i.e. background thread.
.subscribeOn(Schedulers.io())
// report or post the result to main thread.
.observeOn(AndroidSchedulers.mainThread())
// execute this RxJava
.subscribe(new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String result) {
if (hasError.get()) {
if (result.contains("MEETS_DEVICE_INTEGRITY") && result.contains("MEETS_BASIC_INTEGRITY")) {
//Here goes my other code
}
}
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
}
private String getErrorText(Exception e) {
String msg = e.getMessage();
if (msg == null) {
return "Unknown Error";
}
//the error code
int errorCode = Integer.parseInt(msg.replaceAll("\n", "").replaceAll(":(.*)", ""));
switch (errorCode) {
case IntegrityErrorCode.API_NOT_AVAILABLE:
return "API_NOT_AVAILABLE";
case IntegrityErrorCode.NO_ERROR:
return "NO_ERROR";
case IntegrityErrorCode.INTERNAL_ERROR:
return "INTERNAL_ERROR";
case IntegrityErrorCode.NETWORK_ERROR:
return "NETWORK_ERROR";
case IntegrityErrorCode.PLAY_STORE_NOT_FOUND:
return "PLAY_STORE_NOT_FOUND";
case IntegrityErrorCode.PLAY_STORE_ACCOUNT_NOT_FOUND:
return "PLAY_STORE_ACCOUNT_NOT_FOUND";
case IntegrityErrorCode.APP_NOT_INSTALLED:
return "APP_NOT_INSTALLED";
case IntegrityErrorCode.PLAY_SERVICES_NOT_FOUND:
return "PLAY_SERVICES_NOT_FOUND";
case IntegrityErrorCode.APP_UID_MISMATCH:
return "APP_UID_MISMATCH";
case IntegrityErrorCode.TOO_MANY_REQUESTS:
return "TOO_MANY_REQUESTS";
case IntegrityErrorCode.CANNOT_BIND_TO_SERVICE:
return "CANNOT_BIND_TO_SERVICE";
case IntegrityErrorCode.NONCE_TOO_SHORT:
return "NONCE_TOO_SHORT";
case IntegrityErrorCode.NONCE_TOO_LONG:
return "NONCE_TOO_LONG";
case IntegrityErrorCode.GOOGLE_SERVER_UNAVAILABLE:
return "GOOGLE_SERVER_UNAVAILABLE";
case IntegrityErrorCode.NONCE_IS_NOT_BASE64:
return "NONCE_IS_NOT_BASE64";
default:
return "Unknown Error";
}
}
private String generateNonce(int length) {
String nonce = "";
String allowed = getNonce();
for (int i = 0; i < length; i++) {
nonce = nonce.concat(String.valueOf(allowed.charAt((int) Math.floor(Math.random() * allowed.length()))));
}
return nonce;
}
public native String getNonce();
static {
System.loadLibrary("all-keys");
}
I ran into the same problem and I found a solution for this.
You need to specify cloudProjectNumber() when you are working on outside of Google Play, which can be found in google cloud console.
Quote from the doc:
Important: In order to receive and decrypt Integrity API responses,
apps not available on Google Play need to include their Cloud project
number in their requests. You can find this in Project info in the
Google Cloud Console.
So the code should be like this:
IntegrityTokenRequest.builder()
.setNonce(nonce)
.cloudProjectNumber(100004676) // your cloud project number here for dev build
.build());
As I know that the ViewModel should be secluded from the UI/View and contains only the logic that observes the data that's coming from the server or database
In my App, I used REST API "retrofit" and blogger API and I tried to migrate/upgrade the current code to MVVM but there are a few problems, let's go to the code
BloggerAPI Class
public class BloggerAPI {
private static final String BASE_URL =
"https://www.googleapis.com/blogger/v3/blogs/4294497614198718393/posts/";
private static final String KEY = "the Key";
private PostInterFace postInterFace;
private static BloggerAPI INSTANCE;
public BloggerAPI() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
postInterFace = retrofit.create(PostInterFace.class);
}
public static String getBaseUrl() {
return BASE_URL;
}
public static String getKEY() {
return KEY;
}
public static BloggerAPI getINSTANCE() {
if(INSTANCE == null){
INSTANCE = new BloggerAPI();
}
return INSTANCE;
}
public interface PostInterFace {
#GET
Call<PostList> getPostList(#Url String url);
}
public Call<PostList>getPosts(String url){
return postInterFace.getPostList(url);
}
}
this getData method I used in the Mainctivity to retrieve blog posts
public void getData() {
if (getItemsByLabelCalled) return;
progressBar.setVisibility(View.VISIBLE);
String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();
if (token != "") {
url = url + "&pageToken=" + token;
}
if (token == null) {
return;
}
final Call<PostList> postList = BloggerAPI.getINSTANCE().getPosts(url);
postList.enqueue(new Callback<PostList>() {
#Override
public void onResponse(#NonNull Call<PostList> call, #NonNull Response<PostList> response) {
if (response.isSuccessful()) {
progressBar.setVisibility(View.GONE);
PostList list = response.body();
Log.d(TAG, "onResponse: " + response.body());
if (list != null) {
token = list.getNextPageToken();
items.addAll(list.getItems());
adapter.notifyDataSetChanged();
for (int i = 0; i < items.size(); i++) {
items.get(i).setReDefinedID(i);
}
if (sqLiteItemsDBHelper == null || sqLiteItemsDBHelper.getAllItems().isEmpty()) {
SaveInDatabase task = new SaveInDatabase();
Item[] listArr = items.toArray(new Item[0]);
task.execute(listArr);
}
}
} else {
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
int sc = response.code();
switch (sc) {
case 400:
Log.e("Error 400", "Bad Request");
break;
case 404:
Log.e("Error 404", "Not Found");
break;
default:
Log.e("Error", "Generic Error");
}
}
}
#Override
public void onFailure(#NonNull Call<PostList> call, #NonNull Throwable t) {
Toast.makeText(MainActivity.this, "getData error occured", Toast.LENGTH_LONG).show();
Log.e(TAG, "onFailure: " + t.toString());
Log.e(TAG, "onFailure: " + t.getCause());
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
});
}
I created the PostsViewModel to trying to think practically how to migrate the current code to use MVVM
public class PostsViewModel extends ViewModel {
public MutableLiveData<PostList> postListMutableLiveData = new MutableLiveData<>();
public void getData() {
String token = "";
// if (getItemsByLabelCalled) return;
// progressBar.setVisibility(View.VISIBLE);
String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();
if (token != "") {
url = url + "&pageToken=" + token;
}
if (token == null) {
return;
}
BloggerAPI.getINSTANCE().getPosts(url).enqueue(new Callback<PostList>() {
#Override
public void onResponse(Call<PostList> call, Response<PostList> response) {
postListMutableLiveData.setValue(response.body());
}
#Override
public void onFailure(Call<PostList> call, Throwable t) {
}
});
}
}
and it's used thus in MainActivity
postsViewModel = ViewModelProviders.of(this).get(PostsViewModel.class);
postsViewModel.postListMutableLiveData.observe(this, postList -> {
items.addAll(postList.getItems());
adapter.notifyDataSetChanged();
});
now there are two problems using this way of MVVM "ViewModel"
first in the current getData method in the MainActivity it's contains some components that should work only in the View layer like the items list, the recyclerView needs to set View.GONE in case of response unsuccessful, progressBar, emptyView TextView, the adapter that needs to notify if there are changes in the list, and finally I need the context to used the create the Toast messages.
To solve this issue I think to add the UI components and other things into the ViewModel Class and create a constructor like this
public class PostsViewModel extends ViewModel {
Context context;
List<Item> itemList;
PostAdapter postAdapter;
ProgressBar progressBar;
TextView textView;
public PostsViewModel(Context context, List<Item> itemList, PostAdapter postAdapter, ProgressBar progressBar, TextView textView) {
this.context = context;
this.itemList = itemList;
this.postAdapter = postAdapter;
this.progressBar = progressBar;
this.textView = textView;
}
but this is not logically with MVVM arch and for sure cause memory leaking also I will not be able to create the instance of ViewModel with regular way like this
postsViewModel = ViewModelProviders.of(this).get(PostsViewModel.class);
postsViewModel.postListMutableLiveData.observe(this, postList -> {
items.addAll(postList.getItems());
adapter.notifyDataSetChanged();
});
and must be used like this
postsViewModel = new PostsViewModel(this,items,adapter,progressBar,emptyView);
so the first question is How to bind these UI components with the ViewModel?
second in the current getata I used the SaveInDatabase class use the AsyncTask way to save all items in the SQLite database the second question is How to move this class to work with ViewModel? but it also needs to work in the View layer to avoid leaking
the SaveInDatabase Class
static class SaveInDatabase extends AsyncTask<Item, Void, Void> {
#Override
protected Void doInBackground(Item... items) {
List<Item> itemsList = Arrays.asList(items);
// runtimeExceptionDaoItems.create(itemsList);
for (int i = 0 ; i< itemsList.size();i++) {
sqLiteItemsDBHelper.addItem(itemsList.get(i));
Log.e(TAG, "Size :" + sqLiteItemsDBHelper.getAllItems().size());
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
}
}
Actually the question is too broad to answer because there are many ways to implement for this case. First of all, never pass view objects to viewModel. ViewModel is used to notify changes to ui layer with LiveData or rxJava without retaining the view instance. You may try this way.
class PostViewModel extends ViewModel {
private final MutableLiveData<PostList> postListLiveData = new MutableLiveData<PostList>();
private final MutableLiveData<Boolean> loadingStateLiveData = new MutableLiveData<Boolean>();
private String token = "";
public void getData() {
loadingStateLiveData.postValue(true);
// if (getItemsByLabelCalled) return;
// progressBar.setVisibility(View.VISIBLE);
String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();
if (token != "") {
url = url + "&pageToken=" + token;
}
if (token == null) {
return;
}
BloggerAPI.getINSTANCE().getPosts(url).enqueue(new Callback<PostList>() {
#Override
public void onResponse(Call<PostList> call, Response<PostList> response) {
loadingStateLiveData.postValue(false);
postListLiveData.setValue(response.body());
token = response.body().getNextPageToken(); //===> the token
}
#Override
public void onFailure(Call<PostList> call, Throwable t) {
loadingStateLiveData.postValue(false);
}
});
}
public LiveData<PostList> getPostListLiveData(){
return postListLiveData;
}
public LiveData<Boolean> getLoadingStateLiveData(){
return loadingStateLiveData;
}
}
and you may observe the changes from your activity like this.
postsViewModel = ViewModelProviders.of(this).get(PostsViewModel.class);
postsViewModel.getPostListLiveData().observe(this,postList->{
if(isYourPostListEmpty(postlist)) {
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
items.addAll(postList.getItems());
adapter.notifyDataSetChanged();
}else {
recyclerView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
}
});
postsViewModel.getLoadingStateLiveData().observe(this,isLoading->{
if(isLoading) {
progressBar.setVisibility(View.VISIBLE);
}else {
progressBar.setVisibility(View.GONE);
}
});
For my personal prefer, I like using Enum for error handling, but I can't post here as it will make the answer very long. For your second question, use Room from google. It will make you life a lot easier. It work very well with mvvm and it natively support liveData. You can try CodeLab from google to practise using room.
Bonus: You don't need to edit the url like this:
String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();
if (token != "") {
url = url + "&pageToken=" + token;
}
You can use #Path or #query based on your requirements.
As your question is bit broad , I am not giving any source code for the same, Rather mentioning samples which clearly resolves issues mentioned with MVVM.
Clean Code Architecture can be followed which will clearly separate the responsibilities of each layer.
First of all application architecture needs to be restructured so that each layer has designated role in MVVM. You can follow the following pattern for the same.
Only View Model will have access to UI layer
View model will connect with Use Case layer
Use case layer will connect with Data Layer
No layer will have cyclic reference to other components.
So now for Database, Repository will decide, from which section the data needs to be fetched
This can be either from Network or from DataBase.
All these points (except Database part) are covered over Medium Article, were each step is covered with actual API's .
Along with that unit test is also covered.
Libraries used are in this project are
Coroutines
Retrofit
Koin (Dependency Injection) Can be replaced with dagger2 is required
MockWebServer (Testing)
Language: Kotlin
Full Source code can be found over Github
Edit
Kotlin is the official supported language for Android Development now. I suggest you should lean and migrate your java android projects to Kotlin.
Still for converting Kotlin to Java, Go to Menu > Tools > Kotlin > Decompile Kotlin to Java Option
I'm trying to setup a prototype for using graphql across multiple java microservices, which requires me to join multiple graphql schema's into one.
I'm using 2 java-services and the ApolloServer with ApolloGateway; which shows the following schema in the playground:
type Client {
id: ID!
name: String
linkeduser: User
}
type Query {
user(id: ID!): User
users: [User]
client(id: ID!): Client
clients: [Client]
}
type User {
id: ID!
name: String
}
When running the simple query:
query client {
client(id: 1) {
id
name
linkeduser {
id
name
}
}
}
What I expect this to return is a client with a linkeduser; When debugging the client service gets queried, the user service gets queried, yet the response is:
{
"data": {
"client": {
"id": "1",
"name": "Bob",
"linkeduser": null
}
}
}
How do I get a linked user response in my client?
I've tried returning lists of users, a new client object with a list of linkedusers, a single user.
The example of https://github.com/apollographql/federation-jvm is the base of this code, though I've yet to see this working.
Code:
Service 1: Client
#WebServlet(loadOnStartup = 1, urlPatterns = "/graphql")
public class GraphQLService extends GraphQLHttpServlet {
#Override
protected GraphQLConfiguration getConfiguration() {
return GraphQLConfiguration.with(getGraphQLSchema()).build();
}
private static GraphQLSchema getGraphQLSchema() {
InputStream inputStream = client.GraphQLService.class
.getClassLoader().getResourceAsStream("schema.graphqls");
TypeDefinitionRegistry parse = new SchemaParser().parse(inputStream);
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.defaultDataFetcher(GraphQLService::getClient))
.build();
return com.apollographql.federation.graphqljava.Federation.transform(parse, runtimeWiring)
.fetchEntities(env -> env.<List<Map<String, Object>>>getArgument(_Entity.argumentName)
.stream()
.map(values -> {
if ("Client".equals(values.get("__typename"))) {
final Object id = values.get("id");
if (id instanceof String) {
return getSingleClient((String) id);
}
}
return null;
})
.collect(Collectors.toList()))
.resolveEntityType(env -> {
final Object src = env.getObject();
if (src instanceof Client) {
return env.getSchema().getObjectType("Client");
}
return null;
}).build();
}
private static Object getClient(DataFetchingEnvironment environment) {
switch (environment.getFieldDefinition().getName()) {
case "client":
return getSingleClient(environment.getArgument("id"));
case "clients":
return getAllClients();
default:
return null;
}
}
//... extra code with simple getters
}
With this schema :
extend type Query {
client(id: ID!): Client
clients: [Client]
}
type Client #key(fields: "id"){
id: ID!
name: String
}
Service 2: User
#WebServlet(loadOnStartup = 1, urlPatterns = "/graphql")
public class GraphQLService extends GraphQLHttpServlet {
#Override
protected GraphQLConfiguration getConfiguration() {
return GraphQLConfiguration.with(getGraphQLSchema()).build();
}
private static GraphQLSchema getGraphQLSchema() {
InputStream inputStream = user.GraphQLService.class
.getClassLoader().getResourceAsStream("schema.graphqls");
TypeDefinitionRegistry parse = new SchemaParser().parse(inputStream);
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.defaultDataFetcher(GraphQLService::getUser))
.build();
return com.apollographql.federation.graphqljava.Federation.transform(parse, runtimeWiring)
.fetchEntities(env -> env.<List<Map<String, Object>>>getArgument(_Entity.argumentName)
.stream()
.map(values -> {
if ("Client".equals(values.get("__typename"))) {
final Object id = values.get("id");
if (id instanceof String) {
return getSingleUser((String) id);
}
}
return null;
})
.collect(Collectors.toList()))
.resolveEntityType(env -> {
final Object src = env.getObject();
if (src instanceof User) {
return env.getSchema().getObjectType("User");
}
return null;
})
.build();
}
private static Object getUser(DataFetchingEnvironment environment) {
switch (environment.getFieldDefinition().getName()) {
case "user":
return getSingleUser(environment.getArgument("id"));
case "users":
return getAllUsers();
default:
return null;
}
}
//... extra code with simple getters
}
With this schema :
type Query #extends{
user (id: ID!): User
users: [User]
}
type User #key(fields: "id") {
id: ID!
name: String
}
type Client #key(fields: "id") #extends{
id: ID! #external
linkeduser : User
}
Version in POM.xml
<graphql.version>14.0</graphql.version>
<graphql-tools.version>5.2.4</graphql-tools.version>
<graphql-servlet.version>9.0.1</graphql-servlet.version>
<graphql-federation-support.version>0.4.0</graphql-federation-support.version>
In user service, you need to return a pojo of the type client, with a getter for a linkeduser (only the extends fields need to be present):
if ("Client".equals(values.get("__typename"))) {
final Object id = values.get("id");
if (id instanceof String) {
return new Client((String) id, getSingleUser((String) id));
}
}
Also the resolveTypeEntity needs to resolve to said client
I'm new in RxJava so my deep apology if this subject has already been covered or seems pretty easy for some of you. I've thoroughly been searching for a solution without any luck.
Many of my API calls are are based on the original Google LiveData NetworkBoundResource class. Because of the complexity of my business logic I decided to move from LiveData to RxJava. I converted a Kotlin version https://android.jlelse.eu/networkboundresource-with-rxjava-and-kotlin-sealed-classes-1574bc516f82 into a Java one and adapted the rest of the implementation accordingly.
public abstract class NetworkBoundResource<ResultType, RequestType> {
private final String TAG = NetworkBoundResource.class.getSimpleName();
private Flowable<Resource<ResultType>> result;
protected NetworkBoundResource() {
// Lazy db observable.
Flowable<ResultType> dbObservable = Flowable.defer(() ->
loadFromDb()
// Read from disk on Computation Scheduler
.subscribeOn(Schedulers.computation())
);
// Lazy network observable.
Flowable<ResultType> networkObservable = Flowable.defer(() ->
createCall()
// Request API on IO Scheduler
.subscribeOn(Schedulers.io())
// Read/Write to disk on Computation Scheduler
.observeOn(Schedulers.computation())
.doOnNext (request -> {
if (request.isSuccessful()) {
saveCallResult(processResponse(request));
} else {
processInternalError(request);
}
})
.onErrorReturn (throwable -> {
throw Exceptions.propagate(throwable);
})
.flatMap(__ -> loadFromDb())
);
result = NetworkUtils.isNetworkStatusAvailable()
? networkObservable
.map(Resource::success)
.onErrorReturn (t -> Resource.error(t.getMessage(), null))
.observeOn(AndroidSchedulers.mainThread())
.startWith(Resource::loading)
: dbObservable
.map(Resource::success)
.onErrorReturn (t -> Resource.error(t.getMessage(), null))
.observeOn(AndroidSchedulers.mainThread())
.startWith(Resource::loading);
}
public Flowable<Resource<ResultType>> asFlowable() {
return result;
}
private RequestType processResponse(Response<RequestType> response) {
return response.body();
}
private void processInternalError(Response<RequestType> response){
String error = null;
if (response.errorBody() != null) {
try {
error = response.errorBody().string();
} catch (java.io.IOException ignored) {
android.util.Log.e(TAG, "Error while parsing response: " + ignored.getMessage());
}
throw Exceptions.propagate(new Throwable(response.code() + ": " + error));
}
}
protected abstract void saveCallResult(#NonNull RequestType item);
protected abstract Flowable<ResultType> loadFromDb();
protected abstract Flowable<Response<RequestType>> createCall();
}
My View model which subscribes to the Steam
public abstract class ItemListViewModel<T extends IListItem> extends AndroidViewModel {
private MutableLiveData<Resource<List<T>>> itemListObservable;
private Disposable disposable;
protected ItemListViewModel(Application application) {
super(application);
itemListObservable = new MutableLiveData<>();
subscribe();
}
public LiveData<Resource<List<T>>> getItemListObservable() {
return itemListObservable;
}
private void subscribe() {
disposable = loadListItem()
.subscribe(
list -> itemListObservable.setValue(list),
(Throwable t) -> itemListObservable.setValue(Resource.error(t.getMessage(), null))
);
DisposableManager.add(disposable);
}
#Override
protected void onCleared() {
disposable.dispose();
}
protected abstract void init();
protected abstract Flowable<Resource<List<T>>> loadListItem();
}
My generic fragment is handling the resource status
public abstract class ListItemFragment<T extends IListItem> extends Fragment {
protected ItemListAdapter<T> adapter;
protected RecyclerViewBinding binding;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.recycler_view, container, false);
binding.recyclerView.setAdapter(adapter);
binding.recyclerView.setHasFixedSize(true);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
binding.setIsLoading(true);
return binding.getRoot();
}
protected void observeViewModel(ItemListViewModel<T> viewModel) {
// Observe and update the list when the data changes
viewModel.getItemListObservable().observe(this, (#Nullable Resource<List<T>> iObservables) -> {
if (iObservables != null) {
processResponse(iObservables);
}
});
}
private void processResponse(Resource<List<T>> resource) {
switch (resource.status) {
case LOADING:
renderLoadingState();
break;
case SUCCESS:
renderDataState(resource.data);
break;
case ERROR:
renderErrorState(resource.message);
break;
}
}
private void renderLoadingState(){
binding.setIsLoading(true);
adapter.replace(Collections.emptyList());
}
private void renderDataState(List<T> data){
binding.setIsLoading(false);
adapter.replace(data);
}
private void renderErrorState(String error){
Toast.makeText(this.getContext(),error, Toast.LENGTH_SHORT).show();
}
}
The createCall() is never fired though I subscribed to it as explained. Can't see it in my logging interceptor as it was before
My guess was Flowable> was not correctly recognized by Retrofit. I made sure to add the CallAdapterFactory from the squareup repo to my HttpClient
BDRepository(String URL) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.enableComplexMapKeySerialization();
Gson gson = gsonBuilder.create();
retrofit = new Retrofit
.Builder()
.baseUrl(URL)
.client(CustomTrust.getHttpClient())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
My gradle conf
//API request
implementation "com.squareup.retrofit2:retrofit:$rootProject.retrofit"
implementation "com.squareup.retrofit2:converter-gson:$rootProject.retrofit"
implementation "com.squareup.retrofit2:adapter-rxjava2:$rootProject.retrofit"
The API call returns data when trying it in a browser.
When breaking down the code my network is always available so networkObservable is supposed to be the actual stream.
My retrofit service
#GET("items")
Flowable<Response<List<Items>>> getItemList(#Query("lang") String
language);
Any help would be much appreciated
It's a quite specific question but after days stuck in the same place and not getting any response in the Alljoyn forum, I decided to post it here. Maybe someone worked with this framework.
I am doing a project and I need the use of the signal mechanish that Alljoyn framework provides. However, I do need the signals inside a session and the example provides in the api core is sessionless.
In my case I need the service (server) to raise a signal and the client to receive it, but I am getting this error. Following the api samples, I managed to create a simple app that exchange messages through the methods defined in the interface, so I know that the communication is working. In this case, the service waits until the client connects and the send the signal. The client joins the session and right after register the signal and I am getting the BUS_NO_SUCH_INTERFACE error. I tried also to register after and before sending the signal, same problem. I think that for some reason the client does not find the bussignalhandler but I don't know why. I also put it in an external class and it didn't work.
I'm following the example in the core api guide:
https://allseenalliance.org/developers/develop/api-guide/core/android
This is the part where the service register and emit the signal:
SignalEmitter emitter = new SignalEmitter(mySignalInterface, joinerName,
sessionId,
SignalEmitter.GlobalBroadcast.Off);
myInterface = emitter.getInterface(SampleInterface.class);
// Emitting signals myInterface.buttonClicked(1);
myInterface.playerPosition(12, 1, -24);
However, in that example, I can't see a definition for myInterface. and I know it is not a mistake and they meant mySignalInterface because the method getInterface asks for an interface object and mySignalInterface is a class which implements that interface.
I put here the example I created and I'll upload the files in case someone wants to try them.
#BusInterface(name = "org.alljoyn.bus.samples.simple.SimpleInterface")
public interface SimpleInterface {
#BusMethod
String Ping(String inStr) throws BusException;
#BusSignal
public void playerPosition(int x, int y, int z) throws BusException;
}
Service:
Class inside the service to implement the signal and method
class SimpleService implements SimpleInterface, BusObject {
public String Ping(String inStr) {
mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_PING, inStr));
return inStr;
}
public void playerPosition(int x, int y, int z) { /* no implementation needed here*/}
}
Here part of the code that makes the connection in the service:
case CONNECT: {
org.alljoyn.bus.alljoyn.DaemonInit.PrepareDaemon(getApplicationContext());
mBus = new BusAttachment(getPackageName(), BusAttachment.RemoteMessage.Receive);
mBus.registerBusListener(new BusListener());
Status status = mBus.registerBusObject(mSimpleService, "/SimpleService");
if (status != Status.OK) {...}
status = mBus.connect();
logStatus("BusAttachment.connect()", status);
if (status != Status.OK) {...}
int flag = 0;
status = mBus.requestName(SERVICE_NAME, flag);
logStatus(String.format("BusAttachment.requestName(%s, 0x%08x)", SERVICE_NAME, flag), status);
if (status == Status.OK) {
status = mBus.advertiseName(SERVICE_NAME, SessionOpts.TRANSPORT_ANY);
logStatus(String.format("BusAttachement.advertiseName(%s)", SERVICE_NAME), status);
if (status != Status.OK) {...}
}
Mutable.ShortValue contactPort = new Mutable.ShortValue(CONTACT_PORT);
SessionOpts sessionOpts = new SessionOpts();
sessionOpts.traffic = SessionOpts.TRAFFIC_MESSAGES;
sessionOpts.isMultipoint = false;
sessionOpts.proximity = SessionOpts.PROXIMITY_ANY;
sessionOpts.transports = SessionOpts.TRANSPORT_ANY + SessionOpts.TRANSPORT_WFD;
status = mBus.bindSessionPort(contactPort, sessionOpts, new SessionPortListener() {
#Override
public boolean acceptSessionJoiner(short sessionPort, String joiner, SessionOpts sessionOpts) {
return sessionPort == CONTACT_PORT;
}
#Override
public void sessionJoined(short port, int id, String s) {
sessionId = id; joinerName = s; sessionEstablished = true;
}
});
logStatus(String.format("BusAttachment.bindSessionPort(%d, %s)",
contactPort.value, sessionOpts.toString()), status);
if (status != Status.OK) {...}
try {
while (!sessionEstablished) {
Thread.sleep(10);
}
SignalEmitter emitter = new SignalEmitter(mSimpleService, joinerName, sessionId, SignalEmitter.GlobalBroadcast.Off);
SimpleInterface myInterface = emitter.getInterface(SimpleInterface.class);
myInterface.playerPosition(12,1,1);
}
catch (BusException ex) {... }
}
Client:
/*On create of the activity which has a button and a text view to send text to the server */
mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_NULL
&& event.getAction() == KeyEvent.ACTION_UP) {
/* Call the remote object's Ping method. */
Message msg = mBusHandler.obtainMessage(BusHandler.PING,
view.getText().toString());
mBusHandler.sendMessage(msg);
}
return true;
}
});
private static final String SERVICE_NAME = "org.alljoyn.bus.samples.simple";
private static final short CONTACT_PORT=42;
private BusAttachment mBus;
private ProxyBusObject mProxyObj;
private SimpleInterface mSimpleInterface;
case CONNECT: {
org.alljoyn.bus.alljoyn.DaemonInit.PrepareDaemon(getApplicationContext());
mBus = new BusAttachment(getPackageName(), BusAttachment.RemoteMessage.Receive);
mBus.registerBusListener(new BusListener() {
#Override
public void foundAdvertisedName(String name, short transport, String namePrefix) {
if(!mIsConnected) {
Message msg = obtainMessage(JOIN_SESSION);
msg.arg1 = transport;
msg.obj = name;
sendMessage(msg);
}
}
});
Status status = mBus.connect();
logStatus("BusAttachment.connect()", status);
if (Status.OK != status) {...}
status = mBus.findAdvertisedName(SERVICE_NAME);
logStatus(String.format("BusAttachement.findAdvertisedName(%s)", SERVICE_NAME), status);
if (Status.OK != status) {...}
break;
}
case (JOIN_SESSION): {
if (mIsStoppingDiscovery) {
break;
}
short contactPort = CONTACT_PORT;
SessionOpts sessionOpts = new SessionOpts();
sessionOpts.transports = (short)msg.arg1;
Mutable.IntegerValue sessionId = new Mutable.IntegerValue();
Status status = mBus.joinSession((String) msg.obj, contactPort, sessionId, sessionOpts, new SessionListener() {
#Override
public void sessionLost(int sessionId, int reason) {
mIsConnected = false;
logInfo(String.format("MyBusListener.sessionLost(sessionId = %d, reason = %d)", sessionId,reason));
mHandler.sendEmptyMessage(MESSAGE_START_PROGRESS_DIALOG);
}
});
if (status == Status.OK) {
mProxyObj = mBus.getProxyBusObject(SERVICE_NAME,
"/SimpleService",
sessionId.value,
new Class<?>[] { SimpleInterface.class });
mSimpleInterface = mProxyObj.getInterface(SimpleInterface.class);
mSessionId = sessionId.value;
mIsConnected = true;
mHandler.sendEmptyMessage(MESSAGE_STOP_PROGRESS_DIALOG);
}
break;
status = mBus.registerSignalHandlers(this);
if (status != Status.OK) {...}
}
case PING: {
try {
if (mSimpleInterface != null) {
sendUiMessage(MESSAGE_PING, msg.obj);
String reply = mSimpleInterface.Ping((String) msg.obj);
sendUiMessage(MESSAGE_PING_REPLY, reply);
} catch {...}
}
...here some more code...
#BusSignalHandler(iface="org.alljoyn.bus.samples.simple.SimpleInterface", signal="playerPosition")
public void playerPosition(int x, int y, int z) {
sendUiMessage(MESSAGE_POST_TOAST, "Signal captured");
}
In this example, I have my client which has a textview where I can add text and send a ping to the server. This is working if I get rid of the registering signal part. I tried to do it as the api core says, with the Thread.sleep but doesn't work either.
Here I add the code of both of my applications (client & server) based on the samples that are in the alljoyn api.
https://github.com/JavierT/Alljoyn_signal_sample
Please let me know if you have some doubts, it was hard to put all the information in one post.
Thank you in advance.
I think this should solve your problem
Alljoyn BusSignalHandler
either of the following may be used to annotate a signal handler:
#BusSignalHandler(iface = "org.sample.MyInterface", signal = "MySignal")
public void handleSignal(String str)
{
}
#BusSignalHandler(iface = "org.myapp.IMyInterface", signal = "EmitMySignal")
public void handleSignal(String str)
{
}
The first example may be used succesfully when IMyInterface is known to the BusAttachment via a previous call to BusAttachment.registerBusObject(BusObject, String) or BusAttachment.getProxyBusObject(String, String, int, Class[]).
The second example may be used succesfully when IMyInterface is unknown to the BusAttachment.
using the second example should solve your problem.
Also, in your Client program as per the link provided above,
#BusSignalHandler(iface="org.alljoyn.bus.samples.simple.SimpleInterface", signal="playerPosition")
should be replaced with
#BusSignalHandler(iface="org.alljoyn.bus.samples.simpleclient.SimpleInterface", signal="playerPosition")
When you call registerSignalHandlers you must pass an object that implements the interface SimpleInterface and the method playerPosition will be called in that object when you receives a signal.
I believe (I've read the code quickly, I hope I'm not wrong) that you pass your BusHandler object, which does not implements SimpleInterface.
Please change :
private static final String SERVICE_NAME = "org.alljoyn.bus.samples.simple";
to
"org.alljoyn.bus.samples.simple.SimpleInterface" as you have interface name ,
both should be same . as when the Get method of BusObject is called the interface names does not match.