当前位置: 首页 > 知识库问答 >
问题:

为什么调用API或启动协同例程的函数返回空值或null值?

仇龙光
2023-03-14

(免责声明:当人们通过Facebook、Firebase等请求使用异步操作时,会问到数据为空/不正确的问题。我提出这个问题的目的是为每个开始在android中进行异步操作的人提供一个简单的答案)

我试图从我的一个操作中获取数据,当我使用断点或日志调试它时,值就在那里,但当我运行它时,它们总是空的,我如何解决这个问题?

火基

firebaseFirestore.collection("some collection").get()
    .addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
        @Override
        public void onSuccess(QuerySnapshot documentSnapshots) {
            //I want to return these values I receive here... 
        });
//...and use the returned value here.

脸谱网

GraphRequest request = GraphRequest.newGraphPathRequest(
    accessToken,
    "some path",
    new GraphRequest.Callback() {
        @Override
        public void onCompleted(GraphResponse response) {
            //I want to return these values I receive here...
        }
    });
request.executeAsync();
//...and use the returned value here.

静态编程语言协程

var result: SomeResultType? = null
someScope.launch {
    result = someSuspendFunctionToRetrieveSomething()
    //I want to return the value I received here... 
}
Log.d("result", result.toString()) //...but it is still null here.

等等。

共有3个答案

卢勇
2023-03-14

另一个答案解释了如何通过在外部函数中公开类似的基于回调的API来使用基于回调的API。然而,最近Kotlin协同路由变得越来越流行,尤其是在Android上,在使用它们时,通常不鼓励出于此类目的进行回调。Kotlin的方法是使用挂起函数。因此,如果我们的应用程序已经使用协同路由,我建议不要将回调API从第三方库传播到我们的其余代码,而是将它们转换为挂起函数。

将回调转换为挂起

假设我们有以下回调API:

interface Service {
    fun getData(callback: Callback<String>)
}

interface Callback<in T> {
    fun onSuccess(value: T)
    fun onFailure(throwable: Throwable)
}

我们可以使用suspendCoroutine()将其转换为suspend函数:

kotlin prettyprint-override">private val service: Service

suspend fun getData(): String {
    return suspendCoroutine { cont ->
        service.getData(object : Callback<String> {
            override fun onSuccess(value: String) {
                cont.resume(value)
            }

            override fun onFailure(throwable: Throwable) {
                cont.resumeWithException(throwable)
            }
        })
    }
}

这样getData()可以直接同步返回数据,所以其他挂起函数可以很容易地使用它:

suspend fun otherFunction() {
    val data = getData()
    println(data)
}

请注意,我们不必在这里使用with Context(Dispatcher. IO){...}。我们甚至可以从主线程调用getData(),只要我们在协程上下文中(例如在Dispatcher. Main中)-主线程不会被阻塞。

取消

如果回调服务支持取消后台任务,那么最好在调用协同路由本身被取消时取消。让我们向回调API添加一个取消功能:

interface Service {
    fun getData(callback: Callback<String>): Task
}

interface Task {
    fun cancel();
}

现在,服务。getData()返回可用于取消操作的任务。我们可以像以前一样使用它,但有一些小的变化:

suspend fun getData(): String {
    return suspendCancellableCoroutine { cont ->
        val task = service.getData(object : Callback<String> {
            ...
        })

        cont.invokeOnCancellation {
            task.cancel()
        }
    }
}

我们只需要从SuspendCorroutine()切换到SuspendCancelableCorroutine(),然后添加InvokeonConcellation()块。

使用Retrofit的示例

interface GitHubService {
    @GET("users/{user}/repos")
    fun listRepos(@Path("user") user: String): Call<List<Repo>>
}

suspend fun listRepos(user: String): List<Repo> {
    val retrofit = Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build()

    val service = retrofit.create<GitHubService>()

    return suspendCancellableCoroutine { cont ->
        val call = service.listRepos(user)

        call.enqueue(object : Callback<List<Repo>> {
            override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
                if (response.isSuccessful) {
                    cont.resume(response.body()!!)
                } else {
                    // just an example
                    cont.resumeWithException(Exception("Received error response: ${response.message()}"))
                }
            }

            override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
                cont.resumeWithException(t)
            }
        })

        cont.invokeOnCancellation {
            call.cancel()
        }
    }
}

本机支持

在我们开始将回调转换为挂起函数之前,值得检查我们使用的库是否已经支持挂起函数:本机或具有某些扩展名。许多流行的库,如Retrofit或Firebase支持协程和挂起函数。通常,它们要么直接提供/处理挂起函数,要么在其异步任务/调用/等对象之上提供可挂起的等待。这种等待通常被命名为wait()

例如,Retrofit从2.6.0开始直接支持挂起函数:

interface GitHubService {
    @GET("users/{user}/repos")
    suspend fun listRepos(@Path("user") user: String): List<Repo>
}

请注意,我们不仅添加了挂起,而且不再返回调用,而是直接返回结果。现在,我们可以不用所有这些样板文件来使用它:

val repos = service.listRepos(user)
司空思聪
2023-03-14

我已经多次看到这种性质的特殊模式,我认为对正在发生的事情进行解释会有所帮助。模式是一个调用API的函数/方法,将结果分配给回调中的变量,并返回该变量。

以下函数/方法始终返回null,即使API的结果不是null。

科特林

fun foo(): String? {
   var myReturnValue: String? = null
   someApi.addOnSuccessListener { result ->
       myReturnValue = result.value
   }.execute()
   return myReturnValue
}

静态编程语言协程

fun foo(): String? {
   var myReturnValue: String? = null
   lifecycleScope.launch { 
       myReturnValue = someApiSuspendFunction()
   }
   return myReturnValue
}

Java 8

private String fooValue = null;

private String foo() {
    someApi.addOnSuccessListener(result -> fooValue = result.getValue())
        .execute();
    return fooValue;
}

Java7

private String fooValue = null;

private String foo() {
    someApi.addOnSuccessListener(new OnSuccessListener<String>() {
        public void onSuccess(Result<String> result) {
            fooValue = result.getValue();
        }
    }).execute();
    return fooValue;
}

原因是,当您将回调或侦听器传递给API函数时,回调代码只会在API完成其工作后的一段时间内运行。通过将回调传递给API函数,可以对工作进行排队,但当前函数(在本例中为foo())会在工作开始之前和回调代码运行之前立即返回。

或者在上面的协程示例中,启动的协程不太可能在启动它的功能之前完成。

调用API的函数无法返回回调中返回的结果(除非是Kotlin协同例程挂起函数)。另一个答案中解释了解决方案,即让您自己的函数接受回调参数,而不返回任何内容。

或者,如果您正在使用协同路由,您可以使您的函数挂起,而不是启动单独的协同路由。当您有挂起函数时,您必须在代码中的某个地方启动一个协同路由,并在该协同路由中处理结果。通常,您可以在生命周期函数(如onCreate())中启动协同路由,或者在UI回调(如OnClickListener)中启动协同路由。

须敏学
2023-03-14

什么是同步/异步操作?

那么,同步将等待任务完成。在这种情况下,代码执行“自顶向下”。

异步在后台完成任务,并可以在任务完成时通知您。

如果要通过方法/函数从异步操作返回值,可以在方法/函数中定义自己的回调,以便在从这些操作返回值时使用这些值。

下面介绍如何使用Java

首先定义一个接口:

interface Callback {
    void myResponseCallback(YourReturnType result);//whatever your return type is: string, integer, etc.
}

接下来,将方法签名更改为如下所示:

public void foo(final Callback callback) { // make your method, which was previously returning something, return void, and add in the new callback interface.

接下来,无论您以前想在哪里使用这些值,请添加以下行:

callback.myResponseCallback(yourResponseObject);

例如:

@Override
public void onSuccess(QuerySnapshot documentSnapshots) {
    // create your object you want to return here
    String bar = document.get("something").toString();
    callback.myResponseCallback(bar);
})

现在,在前面调用名为foo的方法的地方:

foo(new Callback() {
        @Override
        public void myResponseCallback(YourReturnType result) {
            //here, this result parameter that comes through is your api call result to use, so use this result right here to do any operation you previously wanted to do. 
        }
    });
}

你是如何为Kotlin做到这一点的?(作为一个基本示例,您只关心一个结果)

首先,将方法签名更改为以下内容:

fun foo(callback:(YourReturnType) -> Unit) {
.....

然后,在异步操作的结果中:

firestore.collection("something")
         .document("document").get()
         .addOnSuccessListener { 
             val bar = it.get("something").toString()
             callback(bar)
         }

然后,在您之前调用名为foo的方法的地方,您现在这样做:

foo() { result->
    // here, this result parameter that comes through is 
    // whatever you passed to the callback in the code aboce, 
    // so use this result right here to do any operation 
    // you previously wanted to do. 
}
// Be aware that code outside the callback here will run
// BEFORE the code above, and cannot rely on any data that may
// be set inside the callback.

如果您的foo方法以前接受了参数:

fun foo(value:SomeType, callback:(YourType) -> Unit)

您只需将其更改为:

foo(yourValueHere) { result ->
    // here, this result parameter that comes through is 
    // whatever you passed to the callback in the code aboce, 
    // so use this result right here to do any operation 
    // you previously wanted to do. 
}

这些解决方案展示了如何创建一个方法/函数来返回通过使用回调执行的异步操作的值。

但是,重要的是要了解,如果您对为以下对象创建方法/函数不感兴趣:

@Override
public void onSuccess(SomeApiObjectType someApiResult) {
    // here, this `onSuccess` callback provided by the api 
    // already has the data you're looking for (in this example, 
    // that data would be `someApiResult`).
    // you can simply add all your relevant code which would 
    // be using this result inside this block here, this will 
    // include any manipulation of data, populating adapters, etc. 
    // this is the only place where you will have access to the
    // data returned by the api call, assuming your api follows
    // this pattern
})
 类似资料:
  • (免责声明:人们通过facebook、firebase等请求询问使用异步操作时数据为空/不正确时,会产生很多问题。我提出这个问题的目的是为每个从android异步操作开始的人提供一个简单的答案。) 我试图从我的一个操作中获取数据,当我使用断点或日志调试它时,值就在那里,但当我运行它时,它们总是空的,我该如何解决这个问题呢? 火力基地 脸书 等。

  • 根据JSON规范,表示null值的正确方法是文字。 预期结果: 实际结果:

  • 问题内容: 我是一名编程初学者,对函数的返回值有疑问。 我正在学习Java。 我已经附上了我的书中具有经典选择排序功能的代码。 现在显然来自本书的代码可以正常工作。但是,主要功能中的以下三行是我的问题的基础: int [] a = new int [] {1,9,2,8,3,7,4,6​​,5}; 排序(a); if(ascending(a))System.out.println(“ Works”

  • 问题内容: 我正在使用Postgresql 8.3,并具有以下简单功能,该功能会将a返回 给客户端 现在,我可以使用以下SQL命令来调用此函数并操纵返回的游标,但是游标名称是由PostgreSQL自动生成的 此外,如38.7.3.5中所述,显式地将游标名称声明为函数的输入参数 。返回游标。我可以声明自己的游标名称并使用此游标名称来操纵返回的游标,而不是为我自动生成的Postgresql吗?如果不是

  • 问题内容: 给定一个构造函数,例如 此功能是否应包括健全性检查,例如或?如果此函数应包含完整性检查,则构造函数应返回什么值?甲值,或错误()?下面包括一个示例。 问题答案: 返回。使用可分辨的值(例如)来指示错误不是惯用的。 另外:表达式的计算结果始终为。简化对或的检查。

  • 我甚至使用过List,但仍然得到空指针,但如果我使用livedata,它会在for a循环中成功更新。它不会返回NULL。为什么只有list或Arraylist返回null