Cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Redirect url - need URGENT advice..

ANSWERED

 

Hello, I have an issue with the redirect_url on Android.. 

User is directed to the Authorization Page in Chrome Custom Tabs, when I click "agree", user is not redirected back to application.. 

 

What are the rules and expectancies regarding redirect_url for FitBit API?

I find the documentation on the website very unclear.. 

 

I have already set callback_uri in many different ways, with different outcomes:

 

Local Page with Auth Code, no redirect because not specified:

http://locallhost.com/

 

Error Not Found:

- http://locallhost.com/callback

- http://locallhost.com/redirect

 

What should I use to test locally? 

 

Oops it it not you, it is us.. 

- https://www.fitbit.com

 

Setting in the Application Page are correct. 

Settings in the Manifest are the following, adapted per redirect.. 

 

<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:host="www.locallhost.com/redirect"
android:scheme="http"/>
</intent-filter>

 

Best Answer
0 Votes
65 REPLIES 65

Hey Stuart, 

 

Thanks for the update, I will definitely take a look..

This afternoon, I have a UX and Functional Design Session to see what will go in the application. 

Will pick this up on Sunday or early next week. Will definitely keep you posted! 

 

If I can help you with something, please ask.. 

Best Answer
0 Votes

Hey Stuart,

 

I have checked the code. 

Chrome Custom Tabs is launched with the specified Scope. Have not changed this for now, just want to have a working sketch to work from.. 

 

The redirect is also succesful, I get redirected to the "RedirectActivity". Once arrived at this activity, you create an instance of NxFitbitHelper to be able to use its methods. Next thing you do is call 2 methods from the NXFitbitHelper class. I have no record of the System.out.println() messages in my LogCat, also no further records of subsequent Logs.. I have tested LogCat output for another sketch, and this functions, so settings are ok.. There must be an error in here. Does this function correctly on your system? 

 

//You extract the Code from the "code" variable in the returned Uri
private void setAuthCodeFromIntent(Uri returnUrl) {
authCode = returnUrl.getQueryParameter("code");
System.out.println("Auth Code set to " + authCode);
}

//You get the Token with the Access Code
private void requestAccessToken() {
try {
OAuth20Service serviceCall = getService();
accessToken = serviceCall.getAccessToken(authCode);
System.out.println("accesstoken=" + accessToken);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

 

With respect to "Serialization/ Deserialization", the retrieved JSON will be used in another class. This class will use the JSON as a starting point and build on Processing library to generate a Data Visualization. So this is covered in Processing. So no worries there.. 

 

Do you have an idea where it is going wrong? 

Best Answer
0 Votes

Okay this is strange, cause when I run the code example from within the Git repo I get this output in Logcat

03-19 18:45:57.885 4163-4163/rocks.nxfifteen.poc.myapplication I/System.out: Auth Code set to 1***************************************
03-19 18:45:58.690 4163-4163/rocks.nxfifteen.poc.myapplication I/System.out: accesstoken=com.github.scribejava.core.model.OAuth2AccessToken@********
03-19 18:45:59.328 4163-4163/rocks.nxfifteen.poc.myapplication D/MyActivity: From JSON encodedId: ******
03-19 18:45:59.329 4163-4163/rocks.nxfifteen.poc.myapplication D/MyActivity: From JSON fullName: Stuart Anderson
03-19 18:45:59.329 4163-4163/rocks.nxfifteen.poc.myapplication D/MyActivity: From class encodedId: ******
03-19 18:45:59.329 4163-4163/rocks.nxfifteen.poc.myapplication D/MyActivity: From class fullName: Stuart Anderson
03-19 18:45:59.330 4163-4163/rocks.nxfifteen.poc.myapplication D/MyActivity: From helper class encodedId: ******
03-19 18:45:59.330 4163-4163/rocks.nxfifteen.poc.myapplication D/MyActivity: From helper class fullName: Stuart Anderson
03-19 18:45:59.903 4163-4163/rocks.nxfifteen.poc.myapplication D/MyActivity: steps: 0

Are you running it on a device or within the emulator?

Ionic & Aria, Blaze (retired), Alta (retired), Surge (retired), Charge HR (retired), One (retired), Classic (retired) | Microsoft Surface | Google Pixel 2XL Android FitBit App
Best Answer

I am running on a real device..

On the emulator I get the correct readings as well.

A lot of relief on my side..

I owe you one, do you want Belgian beer? 🙂

 

Question remains why it does not work on a real device? 

 

 

Best Answer
0 Votes

I have been testing it on my Nexus 5. I was just wondering if you'd been using the emulator cause maybe that was the problem, but since its not I'm going to have to have another think about why we're seeing different things.

 

Does anything appear in LogCat when you run the app?

Ionic & Aria, Blaze (retired), Alta (retired), Surge (retired), Charge HR (retired), One (retired), Classic (retired) | Microsoft Surface | Google Pixel 2XL Android FitBit App
Best Answer

No code, not token, no values.. 

On emulator I get the correct values. 

Other applications run on the same device which is now failing.. 

Best Answer
0 Votes

Hi Stuart,

 

I have been running some tests with FitBit API. 

 

- No results on Real Device (Huawei MYA-L41)

- Results on Emulator (Nexus S)

 

Your code is working.. Mine is not..

 

I tried extracting the weight from profile with the following code in the RedirectActivity. I get a result.. 

The result is "0", although I have a weight of 74 added in Profile section. Is this the right code/ could there be something wrong with the API? 

Log.d(TAG, "From JSON weight:" + responseProfile.getJSONObject("user").getString("weight"));

 

Have adapted the scope in the NxFitBitHelper.class to 

@SuppressWarnings("FieldCanBeLocal")
private String apiScope = "activity heartrate sleep profile";

 

In NxFitBitHelper.class I have also added another method to extract activity data for 24/2/2018. On this day I have an activity logged in my FitBit account. 

The new method uses the existing method "makeApiRequest()":

JSONObject getActivity2018() throws InterruptedException, ExecutionException, IOException {
if (apiValueProfile == null) {
apiValueProfile = makeApiRequest("user/-/activities/date/2018-02-24.json");
}
return apiValueProfile;
}

 

In RedirectActivity I have then performed the following actions to retrieve the distance from the first element in Activities. 

JSONObject activityProfile = fitbit.getActivity2018();
Log.d(TAG, "From JSON distance:" + activityProfile.getJSONArray("activities").getJSONObject(0).getString("distance"));

 

An example reply for such a call (FitBit API documentation) would be 

{
    "activities":[
        {
            "activityId":51007,
            "activityParentId":90019,
            "calories":230,
            "description":"7mph",
            "distance":2.04,
            "duration":1097053,
            "hasStartTime":true,
            "isFavorite":true,
            "logId":1154701,
            "name":"Treadmill, 0% Incline",
            "startTime":"00:25",
            "steps":3783
        }
}

The return I get is "org.json.JSONException: No value for activities"..

 

 

 

Best Answer
0 Votes

Afternoon

 

A few things then, first inorder to get a users weight from the profile end point you need to add 'weight' to your scope. If you do that you'll get back to correct weight, other wise the API returns 0. Using the NxFitbitHelper class you can get the weight just using 'fitbit.getFieldFrom("Profile", "weight"))'

 

Next when you create your getActivity2018() method your reusing the apiValueProfile variable, which want work as its been prefilled when you get the first call the profile end point. Instead you need to create a new class variable like this:

private JSONObject apiValueProfile;
private JSONObject apiValueActivity

Then us it like this:

JSONObject getActivity2018() throws InterruptedException, ExecutionException, IOException {
if (this.apiValueActivity == null) {
this.apiValueActivity = makeApiRequest("user/-/activities/date/2018-02-24.json");
}
return this.apiValueActivity;
}

 However, this end points seems 'querky' since I cant get it to return any value in the activities array so I had to change the function to this instead:

JSONObject getActivity2018() throws InterruptedException, ExecutionException, IOException {
if (this.apiValueActivity == null) {
this.apiValueActivity = makeApiRequest("user/-/activities/list.json&afterDate=2018-02-24&offset=0&limit=2&sort=asc");
}
return this.apiValueActivity;
}

Once I did that this code worked fine

Log.d(TAG, "From JSON distance:" + activityProfile.getJSONArray("activities").getJSONObject(0).getString("distance"));
Ionic & Aria, Blaze (retired), Alta (retired), Surge (retired), Charge HR (retired), One (retired), Classic (retired) | Microsoft Surface | Google Pixel 2XL Android FitBit App
Best Answer

Evening

 

For the "Weight API Call", it works now, I missed on that one.. 

 

//Example request for weight
JSONObject getWeight() throws InterruptedException, ExecutionException, IOException {
if (apiValueProfile == null){
apiValueProfile = makeApiRequest("user/-/body/log/weight/goal.json");
}
return apiValueProfile;
}
JSONObject weightprofile = fitbit.getWeight();
Log.d(TAG, "From JSON weight:" + weightprofile.getJSONObject("user").getString("weight"));

 

For the "Activity API Call", a few questions.. 

For clarity underneath the 3 NxFitbitHelper methods and 3 API calls. 

 

//Example request for the user profile
JSONObject getUserProfile() throws InterruptedException, ExecutionException, IOException {
if (apiValueProfile == null) {
apiValueProfile = makeApiRequest("user/-/profile.json");
}
return apiValueProfile;
}

//Example request for weight
JSONObject getWeight() throws InterruptedException, ExecutionException, IOException {
if (apiValueProfile == null){
apiValueProfile = makeApiRequest("user/-/body/log/weight/goal.json");
}
return apiValueProfile;
}

//Example request from 01/01/2018
JSONObject getActivity2018() throws InterruptedException, ExecutionException, IOException {
if (apiValueActivity == null) {
apiValueActivity = makeApiRequest("user/-/activities/list.json&afterDate=2018-02-24&offset=0&limit=2&sort=asc");
}
return apiValueActivity;
}

 

JSONObject responseProfile = fitbit.getUserProfile();
Log.d(TAG, "From JSON encodedId: " + responseProfile.getJSONObject("user").getString("encodedId"));
Log.d(TAG, "From JSON fullName: " + responseProfile.getJSONObject("user").getString("fullName"));

JSONObject weightprofile = fitbit.getWeight();
Log.d(TAG, "From JSON weight:" + weightprofile.getJSONObject("user").getString("weight"));

JSONObject activityProfile = fitbit.getActivity2018();
Log.d(TAG, "From JSON distance:" + activityProfile.getJSONArray("activities").getJSONObject(0).getString("distance"));

 

I use ApiValueProfile both for the Profile and the Weight API calls, and it is valid in both cases. Why do I have to change the variable to get the Activity values? This does not make sense to me, I thought the variable was reset every time? 

 

Can you point me to the documentation for this call please? I cannot find it in the API documentation.. What are the rules for "afterDate", "offset", "limit" and "sort"? 

It is a bit frightening to see you having to perform workarounds for quite basic API requests.. No? 

apiValueActivity = makeApiRequest("user/-/activities/list.json&afterDate=2018-02-24&offset=0&limit=2&sort=asc

 

 

Best Answer
0 Votes

Hey Stuart, hope you are okay..

 

An update. What I would like to do is to use the data obtained from the API EndPoint in a Data Visualization that uses Processing Library. Following Code is used to link the Data Visualization PApplet with the corresponding FrameLayout in the Activity. The API Calls and the Data Visualisation function correctly for a simple sketch. So far everything ok.

 

 public class RedirectActivity extends AppCompatActivity {

private static final String TAG = "MyActivity";
private static TextView data;
private PApplet sketch;

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @@NonNull       int[] grantResults)
    if (sketch != null){
    sketch.onRequestPermissionsResult(requestCode,permissions,grantResults);
}
}

   @Override
    protected void onNewIntent(Intent intent) {
    if (sketch != null){
    sketch.onNewIntent(intent);
    }
    }

 @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_redirect); // For now this will work, but once everything is fine change this to what ever layout your using once a user has completed authorisation

    //retrieve parameter from the Intent
    Uri returnUrl = getIntent().getData();
    if (returnUrl != null) {

    //this allows you to use all the methods in the helper class - request token with code
    NxFitbitHelper fitbit = new NxFitbitHelper();
    fitbit.requestAccessTokenFromIntent(returnUrl);

    try {

    JSONObject activityTimeSeriesDistancesMonth = fitbitsketch.getDistances2802period1month();
    JSONArray activityTimeSeriesDistances =      activityTimeSeriesDistancesMonth.getJSONArray("activities-distance");
    String teststring = activityTimeSeriesDistances.getJSONObject(0).getString("value");
    //NONE OF THESE TWO IS SHOWN IN THE LOGCAT
    Log.d(TAG, "Sketchone Check : " + teststring);
    System.out.println("Sketchone" + teststring);

    } catch (JSONException e) {
    e.printStackTrace();
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (ExecutionException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }


    FrameLayout frameLayout = new FrameLayout(this);
    frameLayout.setId(R.id.SketchFrame);
    sketch = new sketchone();
    PFragment fragment = new PFragment(sketch);
    fragment.setView(frameLayout,this);
    }
   

 

 

Next Step is to work with API Data in the Data Visualisation. I have an extra class for the Data Visualization code called "sketchone()". I initiate the NxFitbitHelper so the methods can be called. The Data Visualization is called after the OAuth2 Process is complete, as you see in the above code.

I call the same methods as before but no data is retrieved.. I believe the problem lies in how I access the NxFitbitHelper or in the sequence of operations, but cannot spot the error. Can you help me?

 

@SuppressLint("NewApi")
public void loadData() throws InterruptedException, ExecutionException, IOException, JSONException {

//NO DATA ARE CALLED, POSITIONS PVECTOR IS EMPTY: Why not is the question..
//this allows you to use all the methods in the helper class - request token with code
NxFitbitHelper fitbitsketch = new NxFitbitHelper();

JSONObject activityTimeSeriesDistancesMonth = fitbitsketch.getDistances2802period1month();
JSONArray activityTimeSeriesDistances = activityTimeSeriesDistancesMonth.getJSONArray("activities-distance");
String teststring = activityTimeSeriesDistances.getJSONObject(0).getString("value");
//NONE OF THESE TWO IS SHOWN IN THE LOGCAT
Log.d(TAG, "Sketchone Check : " + teststring);
System.out.println("Sketchone" + teststring);

 

Best Answer
0 Votes

I'm wonder if the helper class has the authorisation details stored. Since when your creating the sketchone class you create a new helper class it would probably be blank.

 

Could you try updating your sketchone class constructor to take NxFitbitHelper as a parameter, rather than creating a new instance inside sketchone, then use the parameter variable instead?

Ionic & Aria, Blaze (retired), Alta (retired), Surge (retired), Charge HR (retired), One (retired), Classic (retired) | Microsoft Surface | Google Pixel 2XL Android FitBit App
Best Answer
0 Votes

I understand what you are saying, just don't fully understand how to implement it. 

The "Sketchone Class" with the code for the Data Visualization contains the following elements. 

- Setup() with the basics like "Background" and "Fill"

- LoadData() method to retrieve and transform the Data to an acceptable format

- Draw() to draw the actual Data

- DrawGUI() to draw the fixed elements of the User Interface 

 

An example of some code for this Class can be found underneath. DrawGUI() is left out for now.. Question is then how to include a Constructor in this.. What I was thinking is "the authorization process is done, so why do I still need all these parameters, the API is now open for retrieving data.." 

Should I include all the parameters in here again? And then again run through the Authorization? 

 

sketchone (NxFitbitHelper nxFitbitHelper){    }

 

 

public class sketchone extends PApplet {

private static final String TAG = "SketchActivity";

int margin, graphHeight;
float xSpacer;
PVector[] positions;

public void setup() {

background(20);
try {
loadData();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}

public void draw(){
background(20);
for (int i = 0; i<positions.length; i++){
ellipse(positions[i].x, positions[i].y, 15,15);
}
}



@SuppressLint("NewApi")
public void loadData() throws InterruptedException, ExecutionException, IOException, JSONException {

//NO DATA ARE CALLED, POSITIONS PVECTOR IS EMPTY: Why not is the question..
//this allows you to use all the methods in the helper class - request token with code
NxFitbitHelper fitbitsketch = new NxFitbitHelper();

//Parsing Activity Time Series to test EndPoint
JSONObject activityTimeSeriesDistancesMonth = fitbitsketch.getDistances2802period1month();
JSONArray activityTimeSeriesDistances = activityTimeSeriesDistancesMonth.getJSONArray("activities-distance");
String teststring = activityTimeSeriesDistances.getJSONObject(0).getString("value");
//NONE OF THESE TWO IS SHOWN IN THE LOGCAT
Log.d(TAG, "Sketchone Check : " + teststring);
System.out.println("Sketchone" + teststring);

// use a for loop to retrieve all data from the heart rate statistics from 30/01 to 28/02
// this list you can use as input for the Processing sketch
ArrayList<Float> distancelistsketchcrunch = new ArrayList<Float>();
for (int i=0; i < activityTimeSeriesDistances.length(); i++){
String distancessketchcrunch = activityTimeSeriesDistances.getJSONObject(i).getString("value") ;
Float distancesnumbersketchcrunch = Float.parseFloat(distancessketchcrunch);
distancelistsketchcrunch.add (distancesnumbersketchcrunch);
}
System.out.println("DistanceSketch List : " + distancelistsketchcrunch);

ArrayList<Float> distancelistoriginal = new ArrayList<Float>();
for (int i=0; i < activityTimeSeriesDistances.length(); i++){
String distancesoriginal = activityTimeSeriesDistances.getJSONObject(i).getString("value") ;
Float distancesnumbersoriginal = Float.parseFloat(distancesoriginal);
distancelistoriginal.add (distancesnumbersoriginal);
}

ArrayList<String> datetimelistsketch = new ArrayList<String>();
for (int i=0; i < activityTimeSeriesDistances.length(); i++){
String datetimesketch = (activityTimeSeriesDistances.getJSONObject(i).getString("dateTime"));
String dateadaptedsketch = datetimesketch.substring(8,10);
datetimelistsketch.add (dateadaptedsketch);
}
System.out.println("DateTimeSketch List : " + datetimelistsketch);

//calculating positions on the screen
margin = 25;
graphHeight = (height - margin) - margin;
xSpacer = (width - margin - margin)/ (distancelistoriginal.size()-1);

//Need to get the minimum and maximum value out of ArrayList
Collections.sort(distancelistsketchcrunch, Collections.reverseOrder());
float overallMin = distancelistsketchcrunch.get(0);
float overallMax = distancelistsketchcrunch.get(distancelistsketchcrunch.size());

for (int i=0; i<distancelistsketchcrunch.size(); i++){
float adjDistance = map (distancelistsketchcrunch.get(i), overallMin, overallMax,0,graphHeight);
float yPos = height - margin - adjDistance;
float xPos = margin + (xSpacer * i);
positions = new PVector[distancelistsketchcrunch.size()];
positions[i] = new PVector(xPos,yPos);
}
}
}

 

Best Answer
0 Votes

Have a look at this version of sketchone.java.

 

  • But you have to start by creating a class variable - Line 10
  • Use the constructor class to set the class variable from input - Line 12 to 14
  • When you want use the variable you just need to prefice it with this. - Line 47
Ionic & Aria, Blaze (retired), Alta (retired), Surge (retired), Charge HR (retired), One (retired), Classic (retired) | Microsoft Surface | Google Pixel 2XL Android FitBit App
Best Answer
0 Votes

I have adapted the constructor method of the Sketchone Class as you suggested. 

Then adapted the Redirect Activity as follows.

Everything is working with a sketch that does not carry a JSON call. 

Error is still on the Loaddata() method, NullPointerException, no data to perform action on. 

 

    FrameLayout frameLayout = new FrameLayout(this);
frameLayout.setId(R.id.SketchFrame);
sketch = new sketchone(nxFitbitHelper);
PFragment fragment = new PFragment(sketch);
fragment.setView(frameLayout,this);

 

Regarding the functioning on Emulator and not on Real Device, could this have something to do with the Application being in Development and using the DEV redirect_url? 

Best Answer
0 Votes

I'm still not sure why your not seeing any Log cat details on your real device, only the emulator. All I can think is there is something about the way the device is configured in development settings. The only devices I have for testing are a Nexus 5, Nexus 7 and Pixel 2XL and I do get log cat output on each of them.

 

As for your Java question, I think we've reached a point where I sadly can't be of much more help without seeing the full code base. Without a working Sketchone setup I cant test or debug the code to see why your getting a NullPointerException.

 

If your in a possition to share it, but dont want it make public PM. If not I'll help any way I still can, but without understanding the setup and structure of the PFragment class I can't see from your the code you've included whats going wrong.

Ionic & Aria, Blaze (retired), Alta (retired), Surge (retired), Charge HR (retired), One (retired), Classic (retired) | Microsoft Surface | Google Pixel 2XL Android FitBit App
Best Answer
0 Votes

Any luck? 

 

All the best.. 

Best Answer
0 Votes

Hi Stuart, 

 

Have you had the chance yet to take a look at the Application on GitHub?

Best Answer
0 Votes

Afternoon Niels, appologise for the delay. My plan of having a quite, uneventfull Easter weekend didnt go as hoped.

 

I've updated the script and got the graph displayed, there is a pull request waiting for you on Github

Ionic & Aria, Blaze (retired), Alta (retired), Surge (retired), Charge HR (retired), One (retired), Classic (retired) | Microsoft Surface | Google Pixel 2XL Android FitBit App
Best Answer

Ok, great, thanks again for your help.. Happy Easter by the way.. 

 

Best Answer
0 Votes

Sorry I get back to this, but this really is the only blocking issue I still have.. 

For testing on the real device I have, I get redirected to the RedirectActivity and land on a Page with a totally different layout.. Layout displayed is just the basic layout with a "Hello World" Textview in it.. 

 

You mention a setting in "Development Settings" on the device, but this is a different layout.. It is really strange to me. No idea whatsoever? Then I will go check on StackOverflow and other places.. 

Best Answer
0 Votes