transistorsoft / capacitor-background-fetch

Periodic callbacks in the background for both IOS and Android
78 stars 9 forks source link

Question: API call is suspended while the app is in the background? #10

Closed happymago closed 2 years ago

happymago commented 2 years ago

Your Environment

Context

Debug logs

christocracy commented 2 years ago

API call is suspended

What API call?

happymago commented 2 years ago

So my app is a calendar app. The task sends out a request to the server to see if there is new appointment. From chrome inspection, I can see the request never got sent out.

christocracy commented 2 years ago

Why not show all your code?

happymago commented 2 years ago
async initBackgroundFetch() {
  const status = await BackgroundFetch.configure({
    minimumFetchInterval: process.env.VUE_APP_FETCH_INTERVAL_MIN,
    stopOnTerminate: false,  // <-- required
    enableHeadless: true
  }, async (taskId) => {  // <---------------- Event handler.
    await this.performSync();
    BackgroundFetch.finish(taskId);
  }, async (taskId) => {  // <---------------- Event timeout handler
    BackgroundFetch.finish(taskId);
  });

  // Checking BackgroundFetch status:
  if (status !== BackgroundFetch.STATUS_AVAILABLE) {
    // Uh-oh:  we have a problem:
    if (status === BackgroundFetch.STATUS_DENIED) {
      alert('The user explicitly disabled background behavior for this app or for the whole system.');
    } else if (status === BackgroundFetch.STATUS_RESTRICTED) {
      alert('Background updates are unavailable and the user cannot enable them again.')
    }
  }
}

async performSync() {
  return this.getUpcomingEvents(1, 30).then((data) =>  {
    Storage.clear().then(() => {
      data.forEach((element,index) => {
        Storage.set({
          key: index.toString(),
          value: JSON.stringify(element)
        })
      })
    })

  })
}

getUpcomingEvents(startIndex, endIndex){
  const url = `${API_URL}/events/upcoming?userID=${this.getUserID()}&startIndex=${startIndex}&endIndex=${endIndex}`;
  return axios.get(url, { headers: this.authHeader() }).then(response => response.data);      
}
christocracy commented 2 years ago

I suggest you simplify your code for a test. Do a simple http GET request to Google.com using Flutter default http package.

happymago commented 2 years ago

There is nothing complicated in my method. I am not good with javascript.. Learning to use Flutter will take some time too. BTW, is there a way to call javascript method in BackgroundFetchHeadlessTask? I searched online for a while and did not find anything fit. It all mentions using webview. But there is no webview in this case. So far all my logic is in the javascript, I am trying to avoid writing native code to achieve the same thing as the javascript sync method.

christocracy commented 2 years ago

oh, woops. I was thinking we were on the Flutter version. That was Flutter code I posted above. I will do the same with Capacitor version.

christocracy commented 2 years ago

See capacitor-community/http

Here I will implement a simple GET request to Google's Books API, where I will capture and print the "totalItems" property from the response data:

{
  "kind": "books#volumes",
  "totalItems": 3294,
}
.
. <usual imports here>
.
import { Http, HttpResponse } from '@capacitor-community/http';

export class HomePage {

  ngAfterContentInit() {
    this.initBackgroundFetch();
  }

  async initBackgroundFetch() {
    const status = await BackgroundFetch.configure({
      minimumFetchInterval: 15,
      stopOnTerminate: false,
      enableHeadless: true
    }, async (taskId) => {
      console.log('[BackgroundFetch] EVENT:', taskId);

      // Perform your work in an awaited Promise
      const result = await this.performYourWorkHere();

      console.log('[Google Books API] total books:', result.totalItems);

      // [REQUIRED] Signal to the OS that your work is complete.
      BackgroundFetch.finish(taskId);
    }, async (taskId) => {
      // The OS has signalled that your remaining background-time has expired.
      // You must immediately complete your work and signal #finish.
      console.log('[BackgroundFetch] TIMEOUT:', taskId);
      // [REQUIRED] Signal to the OS that your work is complete.
      BackgroundFetch.finish(taskId);
    });
  }

  // A simple HTTP request to Google's books API
  async performYourWorkHere() {
    const options = {
      url: 'https://www.googleapis.com/books/v1/volumes',
      params: { q: '{http}' },
    };

    const response: HttpResponse = await Http.get(options);
    console.log('[HttpResponse] status: ', response.status);
    return response.data;  
  }
}  

With simulated events on my Samsung Galaxy S20 FE @ 12.0.0 and /example app from this repo running in the background, I can see the result of successful http requests being returned.

04-30 13:26:02.458 32716 32716 D TSBackgroundFetch: - Background Fetch event received: capacitor-background-fetch
04-30 13:26:02.483 32716 32716 I Capacitor/Console: - Msg: [BackgroundFetch] EVENT: capacitor-background-fetch

04-30 13:26:04.565 32716 32716 I Capacitor/Console: - Msg: [HttpResponse] status:  200
04-30 13:26:04.567 32716 32716 I Capacitor/Console: - Msg: [Google Books API] total books: 3294

04-30 13:26:04.570 32716   661 V Capacitor/Plugin: To native (Capacitor plugin): callbackId: 95804698, pluginId: BackgroundFetch, methodName: finish
04-30 13:26:04.571 32716   520 D TSBackgroundFetch: - finish: capacitor-background-fetch
04-30 13:26:04.572 32716   520 D TSBackgroundFetch: - jobFinished
happymago commented 2 years ago

Thanks for your code. It does work. With this plugin, the API call can fire without issues even if the app is in the background. I wonder what is the difference vs Axios. I notice the API call with this plugin does not show up in the Network tab in chrome. Maybe that is why?

christocracy commented 2 years ago

The http package I linked above is implementing native iOS / Android requests. It won't show up in the browser.

It is best to do native http requests instead of relying on the browser.

happymago commented 2 years ago

I see. Thank you for the explanation. It might be difficult for me. I initially built a web app and now use capacitor to convert it into mobile apps. I am good for now. At least figured out what is wrong. You can close this ticket.

Ibni-Amin commented 1 year ago

2023-06-06 16:42:50.601 18995-18995 TSBackgroundFetch com..............viewdev D - Background Fetch event received: capacitor-background-fetch 2023-06-06 16:42:50.602 18995-18995 MyHeadlessTask com.............viewdev D BackgroundFetchHeadlessTask onFetch -- CUSTOM IMPLEMENTATION: capacitor-background-fetch 2023-06-06 16:42:50.602 18995-18995 TSBackgroundFetch com.............webviewdev D - finish: capacitor-background-fetch 2023-06-06 16:42:50.602 18995-18995 TSBackgroundFetch com.............webviewdev D - jobFinished

See capacitor-community/http

Here I will implement a simple GET request to Google's Books API, where I will capture and print the "totalItems" property from the response data:

{
  "kind": "books#volumes",
  "totalItems": 3294,
}
.
. <usual imports here>
.
import { Http, HttpResponse } from '@capacitor-community/http';

export class HomePage {

  ngAfterContentInit() {
    this.initBackgroundFetch();
  }

  async initBackgroundFetch() {
    const status = await BackgroundFetch.configure({
      minimumFetchInterval: 15,
      stopOnTerminate: false,
      enableHeadless: true
    }, async (taskId) => {
      console.log('[BackgroundFetch] EVENT:', taskId);

      // Perform your work in an awaited Promise
      const result = await this.performYourWorkHere();

      console.log('[Google Books API] total books:', result.totalItems);

      // [REQUIRED] Signal to the OS that your work is complete.
      BackgroundFetch.finish(taskId);
    }, async (taskId) => {
      // The OS has signalled that your remaining background-time has expired.
      // You must immediately complete your work and signal #finish.
      console.log('[BackgroundFetch] TIMEOUT:', taskId);
      // [REQUIRED] Signal to the OS that your work is complete.
      BackgroundFetch.finish(taskId);
    });
  }

  // A simple HTTP request to Google's books API
  async performYourWorkHere() {
    const options = {
      url: 'https://www.googleapis.com/books/v1/volumes',
      params: { q: '{http}' },
    };

    const response: HttpResponse = await Http.get(options);
    console.log('[HttpResponse] status: ', response.status);
    return response.data;  
  }
}  

With simulated events on my Samsung Galaxy S20 FE @ 12.0.0 and /example app from this repo running in the background, I can see the result of successful http requests being returned.

04-30 13:26:02.458 32716 32716 D TSBackgroundFetch: - Background Fetch event received: capacitor-background-fetch
04-30 13:26:02.483 32716 32716 I Capacitor/Console: - Msg: [BackgroundFetch] EVENT: capacitor-background-fetch

04-30 13:26:04.565 32716 32716 I Capacitor/Console: - Msg: [HttpResponse] status:  200
04-30 13:26:04.567 32716 32716 I Capacitor/Console: - Msg: [Google Books API] total books: 3294

04-30 13:26:04.570 32716   661 V Capacitor/Plugin: To native (Capacitor plugin): callbackId: 95804698, pluginId: BackgroundFetch, methodName: finish
04-30 13:26:04.571 32716   520 D TSBackgroundFetch: - finish: capacitor-background-fetch
04-30 13:26:04.572 32716   520 D TSBackgroundFetch: - jobFinished

Here I am just testing with above example code. But unfortunately I didn't get http status response. My app was in killed State

async performYourWorkHere() { const options = { url: 'https://www.googleapis.com/books/v1/volumes', params: { q: '{http}' }, }; const response: HttpResponse = await Http.get(options); console.log('[HttpResponse] status: ', response.status); return response.data; }