golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.02k stars 17.54k forks source link

x/mobile: cannot use (*proxymobile_AuthStateHandler)(_param_stateHandler_ref) (value of type *proxymobile_AuthStateHandler) as type mobile.AuthStateHandler in assignment #56682

Open DAT4 opened 1 year ago

DAT4 commented 1 year ago

I am trying to use go to make a client library that should be able to used by all the things (mobiles, wasm ...)

Each thing handles state different. So I am trying to inject "state handlers" into the application. I tried with a client version - and it works fine. But when I try with gomobile I get some errors that I don't understand.

I have got similar errors before when injection interfaces in gomobile applications - and I managed to resolve it by moving the interface definition into the same package as where it was called from. That didn't work with the new interfaces.

package mobile

import "chatclient/internal/entity"

type AuthStateHandler interface {
        SetToken(*entity.Token)
        GetToken() *entity.Token
}

type ChatStateHandler interface {
    AddMessage(*entity.Message)
    DeleteMessage(id uint)
    GetMessages() []*entity.Message

    AddUser(*entity.User)
    DeleteUser(id uint)
    GetUsers() []*entity.User

    AddChannel(*entity.Channel)
    DeleteChannel(id uint)
    GetChannels() []*entity.Channel
}
func NewAuthService(stateHandler AuthStateHandler) (*AuthService, error) {
        repo, err := authrepo.NewAuthRepository()
        if err != nil {
                panic(err)
        }
        return &AuthService{
                repo:         repo,
                stateHandler: stateHandler,
        }, nil
}

func NewChat(auth *AuthService, stateHandler ChatStateHandler) *Chat {
        repo, err := chatrepo.NewChatRepository()
        if err != nil {
                panic(err)
        }
        return &Chat{repo, auth, stateHandler}
}
>---->>> gomobile bind -androidapi 21 -target android ./mobile && mv mobile.aar ../mychat/app/libs                                                                              ~/Documents/Personal/userchatclient
gomobile: go build -buildmode=c-shared -o=/tmp/gomobile-work-1643309219/android/src/main/jniLibs/x86/libgojni.so ./gobind failed: exit status 2
# gobind/gobind
gobind/go_mobilemain.go:251:26: cannot use (*proxymobile_AuthStateHandler)(_param_stateHandler_ref) (value of type *proxymobile_AuthStateHandler) as type mobile.AuthStateHandler in assignment:
        *proxymobile_AuthStateHandler does not implement mobile.AuthStateHandler (missing GetToken method)
gobind/go_mobilemain.go:279:26: cannot use (*proxymobile_ChatStateHandler)(_param_stateHandler_ref) (value of type *proxymobile_ChatStateHandler) as type mobile.ChatStateHandler in assignment:
        *proxymobile_ChatStateHandler does not implement mobile.ChatStateHandler (missing AddChannel method)
mknyszek commented 1 year ago

I'm not sure I fully understand the issue or what you're trying to do here. Maybe @hyangah knows?

hyangah commented 1 year ago

The error message is not very comprehensive (I suspect the gomobile/gobind didn't stop early when it observed unsupported types so the go command instead produced this error later while compiling incompletely generated glue code). https://pkg.go.dev/golang.org/x/mobile/cmd/gobind#hdr-Type_restrictions has the set of types gobind (gomobile bind) supports. I guess use of slice in ChatStateHandler can be problematic, and, even though I don't know what's entity.Token but that can be also an issue. You can verify to see what's generated by running the gobind command directly. (golang.org/x/mobile/bind).

DAT4 commented 1 year ago

I figured out that interfaces that are injected into constructors exposed to Android cannot have methods receiving or returning types that are not defined inside the same package that gomobile compiles.

entity.Token is a simple struct defined in another package

type Token struct {
        AccessToken  string
        RefreshToken string
}

func NewToken(accessToken, refreshToken string) *Token {
        return &Token{
                AccessToken:  accessToken,
                RefreshToken: refreshToken,
        }
}

I can solve the problem by defining a Token inside the mobile package and the map the entity.Token internally to mobile.Token

It would be nice to have the ability to use models from external packages as arguments and return values in methods of interfaces. So that I can return the model directly from the internal/entity package instead of redefining every model in the mobile package.

├── internal
│   ├── constants.go
│   ├── entity
│   │   └── models.go
│   ├── interfaces
│   │   ├── auth.go
│   │   ├── chat.go
│   │   └── utils.go
│   └── repository
│       ├── grpc
│       │   ├── authrepo
│       │   └── chatrepo
│       └── http
│           ├── authrepo
│           └── chatrepo
├── mobile
│   ├── auth.go
│   ├── authstatehandler.go
│   ├── chat.go
│   ├── chatstatehandler.go
│   └── messagereceiver.go