Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Need help to make iOS 14 Widget show content from from REST api

The original question is at Apple forum but no one can help. https://developer.apple.com/forums/thread/654967?answerId=622833022#622833022

I decided to ask on SO.

Problem

I'm developing a widget extension that show content from REST api. It show the updated following stock info.

My widget UI

Widgets Code-along, part 3: Advancing timelines doesn't help me.

Investigation brings me a though "it is a bug of iOS beta":

public func timeline(
    for configuration: ConfigurationIntent,
    with context: Context,
    completion: @escaping (Timeline<Entry>) -> ()
  ) {
    print("Provider.timeline: ")

    var entries: [SimpleEntry] = []

    let currentDate = Date()
    entries.append(SimpleEntry(
      date: Calendar.current.date(byAdding: .second, value: 15, to: currentDate)!,
      configuration: configuration
    ))
    
    let timeline = Timeline(entries: entries, policy: reloadPolicy)

    completion(timeline)
  }

Above code print Provider.timeline: 14->19 times in just 1 second.


Here's my code to work with network request without success:

public func timeline(
    for configuration: ConfigurationIntent,
    with context: Context,
    completion: @escaping (Timeline<Entry>) -> ()
  ) {
    print("Provider.timeline: ")
    
    fetchStocks { (stocks: [Stock], error: Bool) in
      print("fetchStocks: stocks: ", stocks) 
      completion(getTimeLineFromStocks(stocks: stocks, for: configuration, with: context, reloadPolicy: .atEnd))
    }
}

func getTimeLineFromStocks(
    stocks: [Stock],
    for configuration: ConfigurationIntent,
    with context: Context,
    reloadPolicy: TimelineReloadPolicy
  ) -> Timeline<Entry> {
    var entries: [SimpleEntry] = []
    let currentDate = Date()
    entries.append(SimpleEntry(
      date: Calendar.current.date(byAdding: .second, value: 15, to: currentDate)!,
      configuration: configuration,
      stocks: stocks
    ))
    
    let timeline = Timeline(entries: entries, policy: reloadPolicy)
    
    return timeline
  }


  func fetchStocks(completion: @escaping ([Stock], Bool) -> Void) {
    // Fetch stocks info from API    
    myStockService.getSearchResults(searchTerm: "FIT", perPage: 5) { results, errorMessage in
      if !errorMessage.isEmpty {
        print("--> Search error: " + errorMessage)
        completion([], true)
      } else if results == nil {
        print("--> Search result with ERROR: nil results")
        completion([], true)
      } else {
        print("--> searchResults: ", results)
        completion(results!, false)
        // reloadTimeline()
      }
    }
  }



// ------- MyStockService.swift -------
// If I set breakpoint I can see the list of stocks
func getSearchResults(searchTerm: String,  perPage: Int, completion: @escaping QueryResult) {
    // 1
    dataTask?.cancel()
    
    // 2
    if var urlComponents = URLComponents(string: "https://****************/my-stocks") {
      urlComponents.query = "foo=bar"

      // 3
      guard let url = urlComponents.url else {
        return
      }
    
      // 4
      dataTask = defaultSession.dataTask(with: url) { [weak self] data, response, error in
        defer {
          self?.dataTask = nil
        }
        
        // 5
        if let error = error {
          self?.errorMessage += "DataTask error: " + error.localizedDescription + "\n"
        } else if
          let data = data,
          let response = response as? HTTPURLResponse,
          response.statusCode == 200 {
          
            // update the: self?.resultItems, self?.errorMessage
            self?.updateSearchResults(data, perPage: perPage)
          
          // 6
          DispatchQueue.main.async {
            completion(self?.resultItems, self?.errorMessage ?? "")
          }
        }
      }
      
      // 7
      dataTask?.resume()
    }
  }

  func updateSearchResults(....) {
     ... This fn convert data to [Stock] and assign it to resultItems 
  }

I got the log:

Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled

fetchStocks: stocks:  []
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled

fetchStocks: stocks:  []
2020-07-23 18:06:38.131476+0700 my-widgetExtension[5315:1272323] libMobileGestalt MobileGestaltCache.c:166: Cache loaded with 4563 pre-cached in CacheData and 53 items in CacheExtra.
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled

fetchStocks: stocks:  []
Provider.timeline: 
Provider.timeline: 
Provider.timeline: 
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled

fetchStocks: stocks:  []
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled

fetchStocks: stocks:  []
2020-07-23 18:06:39.751035+0700 my-widgetExtension[5315:1272388] [connection] nw_resolver_start_query_timer_block_invoke [C1] Query fired: did not receive all answers in time for api-t19.24hmoney.vn:443
2020-07-23 18:06:51.891582+0700 my-widgetExtension[5315:1272323] [widget] No intent in timeline(for:with:completion:)

Above code doesn't update or show Stock to the widget UI, it sometimes has some strange crashed.

Anyone can help me to make it work?

like image 410
Neo.Mxn0 Avatar asked Jul 23 '20 10:07

Neo.Mxn0


People also ask

How do I create a custom widget in iOS 14?

Touch and hold the Lock Screen until the Customize button appears, then tap Customize. Tap the box above or below the time to see the widgets that you can add to your Lock Screen. Tap or drag the widgets that you want to add. Tap Done.


1 Answers

I return to widget development after ~3 months of waiting for it to be stable.

I can confirm that my above previous code basically works since I upgrade to Xcode Version 12.2 beta 2 (12B5025f) yesterday. But I need to delete the widget and create a new one to avoid outdated code.

--- Updated ---

iOS limit widget refreshment by some factor so content refreshing from API every 5 mins might not always work as I expected.

Please see the official docs about Refreshing Widgets Efficiently

As mentioned in the docs, use the following approaches to optimize your widget refreshes:

  • Have the containing app prepare data for the widget in advance of when the widget needs it. Use a shared group container to store the data.
  • Use background processing time in your app to keep shared data up to date. For more information, see Updating Your App with Background App Refresh.
  • Choose the most appropriate refresh policy for the information being shown, as described in the preceding section.
  • Call reloadTimelines(ofKind:) only when information the widget is currently displaying changes.
  • When your app is in the foreground, has an active media session, or is using the standard location service, refreshes don’t count against the widget’s daily limit
like image 157
Neo.Mxn0 Avatar answered Oct 01 '22 21:10

Neo.Mxn0