Skip to content

Android: fetchProducts({ type: 'all' }) returns [] — FetchProductsResultAll dropped in ExpoIapModule.kt #192

Description

@vladyslavbaranov25

Title

Android: fetchProducts({ type: 'all' }) returns [] — FetchProductsResultAll dropped in ExpoIapModule.kt


Description

Summary

On Android, fetchProducts({ skus, type: 'all' }) resolves to an empty array [], even when the exact same SKUs return correctly via fetchProducts({ skus, type: 'subs' }). So 'subs' (and 'in-app') work, but 'all' silently returns nothing.

Environment

  • expo-iap: 4.3.1
  • openiap-google: 2.2.1 (pinned in openiap-versions.json)
  • React Native: 0.83.4, Expo SDK 55
  • Platform: Android (Google Play Billing)

Reproduction

With one or more active subscriptions configured in Play Console:

// returns []  ❌
await fetchProducts({ skus: ['com.example.monthly'], type: 'all' });

// returns [{ id: 'com.example.monthly', type: 'subs', ... }]  ✅
await fetchProducts({ skus: ['com.example.monthly'], type: 'subs' });

Expected behavior

type: 'all' returns the union of in-app + subscription products (the docs state: "a mixed array for 'all'").

Actual behavior

type: 'all' → []. 'subs' and 'in-app' each work individually.

Root cause

In android/src/main/java/expo/modules/iap/ExpoIapModule.kt, the fetchProducts function maps only two of the three sealed FetchProductsResult subtypes returned by openiap-google:

val result = openIap.fetchProducts(request)
val payload = when (result) {
    is FetchProductsResultProducts      -> result.value.orEmpty().map { it.toJson() }
    is FetchProductsResultSubscriptions -> result.value.orEmpty().map { it.toJson() }
    else -> emptyList<Map<String, Any?>>()   // ← FetchProductsResultAll lands here
}

The flow is correct right up to this point:

  • parseProductQueryType("all") → ProductQueryType.All (correct).
  • openiap-google handles All correctly: it queries Google Play for both "inapp" and "subs" (each wrapped in its own runCatching, so an empty in-app half does not drop the subs half) and returns a populated FetchProductsResultAll (List<ProductOrSubscription>).
  • expo-iap then discards it: there is no is FetchProductsResultAll branch (the type isn't even imported), so the populated result hits else -> emptyList().

'subs' works because openiap returns FetchProductsResultSubscriptions, which is matched.

This branch is still missing on main.

Proposed fix

Add the missing branch (and import dev.hyo.openiap.FetchProductsResultAll):

when (result) {
    is FetchProductsResultProducts      -> result.value.orEmpty().map { it.toJson() }
    is FetchProductsResultSubscriptions -> result.value.orEmpty().map { it.toJson() }
    is FetchProductsResultAll           -> result.value.orEmpty().map { it.toJson() }  // add
    else -> emptyList<Map<String, Any?>>()
}

Workaround

Query 'subs' and 'in-app' separately and merge the results, avoiding 'all' entirely.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions