diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
index 72033fc121..8280300caa 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online
         [Test]
         public void TestOffline()
         {
-            AddStep("Populate", () => display.Users = getUsers());
+            AddStep("Populate with offline test users", () => display.Users = getUsers());
         }
 
         [Test]
@@ -80,14 +80,7 @@ namespace osu.Game.Tests.Visual.Online
 
         private class TestFriendDisplay : FriendDisplay
         {
-            public void Fetch()
-            {
-                base.APIStateChanged(API, APIState.Online);
-            }
-
-            public override void APIStateChanged(IAPIProvider api, APIState state)
-            {
-            }
+            public void Fetch() => PerformFetch();
         }
     }
 }
diff --git a/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs
index 9591d53b24..ec183adbbc 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Online
         [Test]
         public void TestOnlineStateVisibility()
         {
-            AddStep("set status to online", () => ((DummyAPIAccess)API).State = APIState.Online);
+            AddStep("set status to online", () => ((DummyAPIAccess)API).SetState(APIState.Online));
 
             AddUntilStep("children are visible", () => onlineView.ViewTarget.IsPresent);
             AddUntilStep("loading animation is not visible", () => !onlineView.LoadingSpinner.IsPresent);
@@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online
         [Test]
         public void TestOfflineStateVisibility()
         {
-            AddStep("set status to offline", () => ((DummyAPIAccess)API).State = APIState.Offline);
+            AddStep("set status to offline", () => ((DummyAPIAccess)API).SetState(APIState.Offline));
 
             AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
             AddUntilStep("loading animation is not visible", () => !onlineView.LoadingSpinner.IsPresent);
@@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online
         [Test]
         public void TestConnectingStateVisibility()
         {
-            AddStep("set status to connecting", () => ((DummyAPIAccess)API).State = APIState.Connecting);
+            AddStep("set status to connecting", () => ((DummyAPIAccess)API).SetState(APIState.Connecting));
 
             AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
             AddUntilStep("loading animation is visible", () => onlineView.LoadingSpinner.IsPresent);
@@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online
         [Test]
         public void TestFailingStateVisibility()
         {
-            AddStep("set status to failing", () => ((DummyAPIAccess)API).State = APIState.Failing);
+            AddStep("set status to failing", () => ((DummyAPIAccess)API).SetState(APIState.Failing));
 
             AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
             AddUntilStep("loading animation is visible", () => onlineView.LoadingSpinner.IsPresent);
diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
index cb4884aa51..c4563d5844 100644
--- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps
                 if (checkLocalCache(set, beatmap))
                     return;
 
-                if (api?.State != APIState.Online)
+                if (api?.State.Value != APIState.Online)
                     return;
 
                 var req = new GetBeatmapRequest(beatmap);
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 4ea5c192fe..b916339a53 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -78,26 +78,8 @@ namespace osu.Game.Online.API
 
         private void onTokenChanged(ValueChangedEvent<OAuthToken> e) => config.Set(OsuSetting.Token, config.Get<bool>(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty);
 
-        private readonly List<IOnlineComponent> components = new List<IOnlineComponent>();
-
         internal new void Schedule(Action action) => base.Schedule(action);
 
-        /// <summary>
-        /// Register a component to receive API events.
-        /// Fires <see cref="IOnlineComponent.APIStateChanged"/> once immediately to ensure a correct state.
-        /// </summary>
-        /// <param name="component"></param>
-        public void Register(IOnlineComponent component)
-        {
-            Schedule(() => components.Add(component));
-            component.APIStateChanged(this, state);
-        }
-
-        public void Unregister(IOnlineComponent component)
-        {
-            Schedule(() => components.Remove(component));
-        }
-
         public string AccessToken => authentication.RequestAccessToken();
 
         /// <summary>
@@ -109,7 +91,7 @@ namespace osu.Game.Online.API
         {
             while (!cancellationToken.IsCancellationRequested)
             {
-                switch (State)
+                switch (State.Value)
                 {
                     case APIState.Failing:
                         //todo: replace this with a ping request.
@@ -131,12 +113,12 @@ namespace osu.Game.Online.API
                         // work to restore a connection...
                         if (!HasLogin)
                         {
-                            State = APIState.Offline;
+                            state.Value = APIState.Offline;
                             Thread.Sleep(50);
                             continue;
                         }
 
-                        State = APIState.Connecting;
+                        state.Value = APIState.Connecting;
 
                         // save the username at this point, if the user requested for it to be.
                         config.Set(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
@@ -162,20 +144,20 @@ namespace osu.Game.Online.API
                             failureCount = 0;
 
                             //we're connected!
-                            State = APIState.Online;
+                            state.Value = APIState.Online;
                         };
 
                         if (!handleRequest(userReq))
                         {
-                            if (State == APIState.Connecting)
-                                State = APIState.Failing;
+                            if (State.Value == APIState.Connecting)
+                                state.Value = APIState.Failing;
                             continue;
                         }
 
                         // The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
                         // Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
                         // before actually going online.
-                        while (State > APIState.Offline && State < APIState.Online)
+                        while (State.Value > APIState.Offline && State.Value < APIState.Online)
                             Thread.Sleep(500);
 
                         break;
@@ -224,7 +206,7 @@ namespace osu.Game.Online.API
 
         public void Login(string username, string password)
         {
-            Debug.Assert(State == APIState.Offline);
+            Debug.Assert(State.Value == APIState.Offline);
 
             ProvidedUsername = username;
             this.password = password;
@@ -232,7 +214,7 @@ namespace osu.Game.Online.API
 
         public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
         {
-            Debug.Assert(State == APIState.Offline);
+            Debug.Assert(State.Value == APIState.Offline);
 
             var req = new RegistrationRequest
             {
@@ -276,7 +258,7 @@ namespace osu.Game.Online.API
                 req.Perform(this);
 
                 // we could still be in initialisation, at which point we don't want to say we're Online yet.
-                if (IsLoggedIn) State = APIState.Online;
+                if (IsLoggedIn) state.Value = APIState.Online;
 
                 failureCount = 0;
                 return true;
@@ -293,27 +275,12 @@ namespace osu.Game.Online.API
             }
         }
 
-        private APIState state;
+        private readonly Bindable<APIState> state = new Bindable<APIState>();
 
-        public APIState State
-        {
-            get => state;
-            private set
-            {
-                if (state == value)
-                    return;
-
-                APIState oldState = state;
-                state = value;
-
-                log.Add($@"We just went {state}!");
-                Schedule(() =>
-                {
-                    components.ForEach(c => c.APIStateChanged(this, state));
-                    OnStateChange?.Invoke(oldState, state);
-                });
-            }
-        }
+        /// <summary>
+        /// The current connectivity state of the API.
+        /// </summary>
+        public IBindable<APIState> State => state;
 
         private bool handleWebException(WebException we)
         {
@@ -343,9 +310,9 @@ namespace osu.Game.Online.API
                         // we might try again at an api level.
                         return false;
 
-                    if (State == APIState.Online)
+                    if (State.Value == APIState.Online)
                     {
-                        State = APIState.Failing;
+                        state.Value = APIState.Failing;
                         flushQueue();
                     }
 
@@ -362,10 +329,6 @@ namespace osu.Game.Online.API
             lock (queue) queue.Enqueue(request);
         }
 
-        public event StateChangeDelegate OnStateChange;
-
-        public delegate void StateChangeDelegate(APIState oldState, APIState newState);
-
         private void flushQueue(bool failOldRequests = true)
         {
             lock (queue)
@@ -392,7 +355,7 @@ namespace osu.Game.Online.API
             // Scheduled prior to state change such that the state changed event is invoked with the correct user present
             Schedule(() => LocalUser.Value = createGuestUser());
 
-            State = APIState.Offline;
+            state.Value = APIState.Offline;
         }
 
         private static User createGuestUser() => new GuestUser();
diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs
index 7800241904..1672d0495d 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.cs
@@ -21,34 +21,23 @@ namespace osu.Game.Online.API
 
         public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
 
-        public bool IsLoggedIn => State == APIState.Online;
+        public bool IsLoggedIn => State.Value == APIState.Online;
 
         public string ProvidedUsername => LocalUser.Value.Username;
 
         public string Endpoint => "http://localhost";
 
-        private APIState state = APIState.Online;
-
-        private readonly List<IOnlineComponent> components = new List<IOnlineComponent>();
-
         /// <summary>
         /// Provide handling logic for an arbitrary API request.
         /// </summary>
         public Action<APIRequest> HandleRequest;
 
-        public APIState State
-        {
-            get => state;
-            set
-            {
-                if (state == value)
-                    return;
+        private readonly Bindable<APIState> state = new Bindable<APIState>(APIState.Online);
 
-                state = value;
-
-                Scheduler.Add(() => components.ForEach(c => c.APIStateChanged(this, value)));
-            }
-        }
+        /// <summary>
+        /// The current connectivity state of the API.
+        /// </summary>
+        public IBindable<APIState> State => state;
 
         public DummyAPIAccess()
         {
@@ -72,17 +61,6 @@ namespace osu.Game.Online.API
             return Task.CompletedTask;
         }
 
-        public void Register(IOnlineComponent component)
-        {
-            Scheduler.Add(delegate { components.Add(component); });
-            component.APIStateChanged(this, state);
-        }
-
-        public void Unregister(IOnlineComponent component)
-        {
-            Scheduler.Add(delegate { components.Remove(component); });
-        }
-
         public void Login(string username, string password)
         {
             LocalUser.Value = new User
@@ -91,13 +69,13 @@ namespace osu.Game.Online.API
                 Id = 1001,
             };
 
-            State = APIState.Online;
+            state.Value = APIState.Online;
         }
 
         public void Logout()
         {
             LocalUser.Value = new GuestUser();
-            State = APIState.Offline;
+            state.Value = APIState.Offline;
         }
 
         public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
@@ -105,5 +83,7 @@ namespace osu.Game.Online.API
             Thread.Sleep(200);
             return null;
         }
+
+        public void SetState(APIState newState) => state.Value = newState;
     }
 }
diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs
index dff6d0b2ce..256d2ed151 100644
--- a/osu.Game/Online/API/IAPIProvider.cs
+++ b/osu.Game/Online/API/IAPIProvider.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Online.API
         /// </summary>
         string Endpoint { get; }
 
-        APIState State { get; }
+        IBindable<APIState> State { get; }
 
         /// <summary>
         /// Queue a new request.
@@ -61,18 +61,6 @@ namespace osu.Game.Online.API
         /// <param name="request">The request to perform.</param>
         Task PerformAsync(APIRequest request);
 
-        /// <summary>
-        /// Register a component to receive state changes.
-        /// </summary>
-        /// <param name="component">The component to register.</param>
-        void Register(IOnlineComponent component);
-
-        /// <summary>
-        /// Unregisters a component to receive state changes.
-        /// </summary>
-        /// <param name="component">The component to unregister.</param>
-        void Unregister(IOnlineComponent component);
-
         /// <summary>
         /// Attempt to login using the provided credentials. This is a non-blocking operation.
         /// </summary>
diff --git a/osu.Game/Online/API/IOnlineComponent.cs b/osu.Game/Online/API/IOnlineComponent.cs
deleted file mode 100644
index da6b784759..0000000000
--- a/osu.Game/Online/API/IOnlineComponent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-namespace osu.Game.Online.API
-{
-    public interface IOnlineComponent
-    {
-        void APIStateChanged(IAPIProvider api, APIState state);
-    }
-}
diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs
index 2acee394a6..3a5c2e181f 100644
--- a/osu.Game/Online/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Online/Leaderboards/Leaderboard.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using osu.Framework.Allocation;
+using osu.Framework.Bindables;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Colour;
@@ -22,7 +23,7 @@ using osuTK.Graphics;
 
 namespace osu.Game.Online.Leaderboards
 {
-    public abstract class Leaderboard<TScope, TScoreInfo> : Container, IOnlineComponent
+    public abstract class Leaderboard<TScope, TScoreInfo> : Container
     {
         private const double fade_duration = 300;
 
@@ -242,16 +243,13 @@ namespace osu.Game.Online.Leaderboards
 
         private ScheduledDelegate pendingUpdateScores;
 
+        private readonly IBindable<APIState> apiState = new Bindable<APIState>();
+
         [BackgroundDependencyLoader]
         private void load()
         {
-            api?.Register(this);
-        }
-
-        protected override void Dispose(bool isDisposing)
-        {
-            base.Dispose(isDisposing);
-            api?.Unregister(this);
+            apiState.BindTo(api.State);
+            apiState.BindValueChanged(onlineStateChanged, true);
         }
 
         public void RefreshScores() => UpdateScores();
@@ -260,9 +258,9 @@ namespace osu.Game.Online.Leaderboards
 
         protected abstract bool IsOnlineScope { get; }
 
-        public void APIStateChanged(IAPIProvider api, APIState state)
+        private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
         {
-            switch (state)
+            switch (state.NewValue)
             {
                 case APIState.Online:
                 case APIState.Offline:
@@ -271,7 +269,7 @@ namespace osu.Game.Online.Leaderboards
 
                     break;
             }
-        }
+        });
 
         protected void UpdateScores()
         {
diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs
index b52e3d9e3c..295d079d29 100644
--- a/osu.Game/Online/OnlineViewContainer.cs
+++ b/osu.Game/Online/OnlineViewContainer.cs
@@ -2,6 +2,7 @@
 // See the LICENCE file in the repository root for full licence text.
 
 using osu.Framework.Allocation;
+using osu.Framework.Bindables;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Graphics.UserInterface;
@@ -14,7 +15,7 @@ namespace osu.Game.Online
     /// A <see cref="Container"/> for displaying online content which require a local user to be logged in.
     /// Shows its children only when the local user is logged in and supports displaying a placeholder if not.
     /// </summary>
-    public abstract class OnlineViewContainer : Container, IOnlineComponent
+    public abstract class OnlineViewContainer : Container
     {
         protected LoadingSpinner LoadingSpinner { get; private set; }
 
@@ -34,8 +35,10 @@ namespace osu.Game.Online
             this.placeholderMessage = placeholderMessage;
         }
 
+        private readonly IBindable<APIState> apiState = new Bindable<APIState>();
+
         [BackgroundDependencyLoader]
-        private void load()
+        private void load(IAPIProvider api)
         {
             InternalChildren = new Drawable[]
             {
@@ -46,18 +49,19 @@ namespace osu.Game.Online
                     Alpha = 0,
                 }
             };
+
+            apiState.BindTo(api.State);
+            apiState.BindValueChanged(onlineStateChanged, true);
         }
 
-        protected override void LoadComplete()
+        [BackgroundDependencyLoader]
+        private void load()
         {
-            base.LoadComplete();
-
-            API.Register(this);
         }
 
-        public virtual void APIStateChanged(IAPIProvider api, APIState state)
+        private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
         {
-            switch (state)
+            switch (state.NewValue)
             {
                 case APIState.Offline:
                     PopContentOut(Content);
@@ -79,7 +83,7 @@ namespace osu.Game.Online
                     placeholder.FadeOut(transform_duration / 2, Easing.OutQuint);
                     break;
             }
-        }
+        });
 
         /// <summary>
         /// Applies a transform to the online content to make it hidden.
@@ -90,11 +94,5 @@ namespace osu.Game.Online
         /// Applies a transform to the online content to make it visible.
         /// </summary>
         protected virtual void PopContentIn(Drawable content) => content.FadeIn(transform_duration, Easing.OutQuint);
-
-        protected override void Dispose(bool isDisposing)
-        {
-            API?.Unregister(this);
-            base.Dispose(isDisposing);
-        }
     }
 }
diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs
index 89d8cbde11..58ede5502a 100644
--- a/osu.Game/Overlays/AccountCreationOverlay.cs
+++ b/osu.Game/Overlays/AccountCreationOverlay.cs
@@ -2,6 +2,7 @@
 // See the LICENCE file in the repository root for full licence text.
 
 using osu.Framework.Allocation;
+using osu.Framework.Bindables;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
@@ -17,7 +18,7 @@ using osuTK.Graphics;
 
 namespace osu.Game.Overlays
 {
-    public class AccountCreationOverlay : OsuFocusedOverlayContainer, IOnlineComponent
+    public class AccountCreationOverlay : OsuFocusedOverlayContainer
     {
         private const float transition_time = 400;
 
@@ -30,10 +31,13 @@ namespace osu.Game.Overlays
             Origin = Anchor.Centre;
         }
 
+        private readonly IBindable<APIState> apiState = new Bindable<APIState>();
+
         [BackgroundDependencyLoader]
         private void load(OsuColour colours, IAPIProvider api)
         {
-            api.Register(this);
+            apiState.BindTo(api.State);
+            apiState.BindValueChanged(apiStateChanged, true);
 
             Children = new Drawable[]
             {
@@ -97,9 +101,9 @@ namespace osu.Game.Overlays
             this.FadeOut(100);
         }
 
-        public void APIStateChanged(IAPIProvider api, APIState state)
+        private void apiStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
         {
-            switch (state)
+            switch (state.NewValue)
             {
                 case APIState.Offline:
                 case APIState.Failing:
@@ -112,6 +116,6 @@ namespace osu.Game.Overlays
                     Hide();
                     break;
             }
-        }
+        });
     }
 }
diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs
index 8135b83a03..6c58ed50fb 100644
--- a/osu.Game/Overlays/DashboardOverlay.cs
+++ b/osu.Game/Overlays/DashboardOverlay.cs
@@ -33,6 +33,15 @@ namespace osu.Game.Overlays
         {
         }
 
+        private readonly IBindable<APIState> apiState = new Bindable<APIState>();
+
+        [BackgroundDependencyLoader]
+        private void load(IAPIProvider api)
+        {
+            apiState.BindTo(api.State);
+            apiState.BindValueChanged(onlineStateChanged, true);
+        }
+
         [BackgroundDependencyLoader]
         private void load()
         {
@@ -130,13 +139,13 @@ namespace osu.Game.Overlays
             }
         }
 
-        public override void APIStateChanged(IAPIProvider api, APIState state)
+        private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
         {
             if (State.Value == Visibility.Hidden)
                 return;
 
             Header.Current.TriggerChange();
-        }
+        });
 
         protected override void Dispose(bool isDisposing)
         {
diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs
index bd6b07c65f..6f56d95929 100644
--- a/osu.Game/Overlays/FullscreenOverlay.cs
+++ b/osu.Game/Overlays/FullscreenOverlay.cs
@@ -12,7 +12,7 @@ using osuTK.Graphics;
 
 namespace osu.Game.Overlays
 {
-    public abstract class FullscreenOverlay<T> : WaveOverlayContainer, IOnlineComponent, INamedOverlayComponent
+    public abstract class FullscreenOverlay<T> : WaveOverlayContainer, INamedOverlayComponent
         where T : OverlayHeader
     {
         public virtual string IconTexture => Header?.Title.IconTexture ?? string.Empty;
@@ -86,21 +86,5 @@ namespace osu.Game.Overlays
         protected virtual void PopOutComplete()
         {
         }
-
-        protected override void LoadComplete()
-        {
-            base.LoadComplete();
-            API.Register(this);
-        }
-
-        protected override void Dispose(bool isDisposing)
-        {
-            base.Dispose(isDisposing);
-            API?.Unregister(this);
-        }
-
-        public virtual void APIStateChanged(IAPIProvider api, APIState state)
-        {
-        }
     }
 }
diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs
index 312271316a..c254cdf290 100644
--- a/osu.Game/Overlays/OverlayView.cs
+++ b/osu.Game/Overlays/OverlayView.cs
@@ -2,6 +2,7 @@
 // See the LICENCE file in the repository root for full licence text.
 
 using osu.Framework.Allocation;
+using osu.Framework.Bindables;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Online.API;
@@ -15,7 +16,7 @@ namespace osu.Game.Overlays
     /// Automatically performs a data fetch on load.
     /// </remarks>
     /// <typeparam name="T">The type of the API response.</typeparam>
-    public abstract class OverlayView<T> : CompositeDrawable, IOnlineComponent
+    public abstract class OverlayView<T> : CompositeDrawable
         where T : class
     {
         [Resolved]
@@ -29,10 +30,13 @@ namespace osu.Game.Overlays
             AutoSizeAxes = Axes.Y;
         }
 
-        protected override void LoadComplete()
+        private readonly IBindable<APIState> apiState = new Bindable<APIState>();
+
+        [BackgroundDependencyLoader]
+        private void load()
         {
-            base.LoadComplete();
-            API.Register(this);
+            apiState.BindTo(API.State);
+            apiState.BindValueChanged(onlineStateChanged, true);
         }
 
         /// <summary>
@@ -59,20 +63,19 @@ namespace osu.Game.Overlays
             API.Queue(request);
         }
 
-        public virtual void APIStateChanged(IAPIProvider api, APIState state)
+        private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
         {
-            switch (state)
+            switch (state.NewValue)
             {
                 case APIState.Online:
                     PerformFetch();
                     break;
             }
-        }
+        });
 
         protected override void Dispose(bool isDisposing)
         {
             request?.Cancel();
-            API?.Unregister(this);
             base.Dispose(isDisposing);
         }
     }
diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index 9e358d0cf5..873272bf12 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -13,6 +13,7 @@ using osu.Game.Online.API;
 using osuTK;
 using osu.Game.Users;
 using System.ComponentModel;
+using osu.Framework.Bindables;
 using osu.Game.Graphics;
 using osuTK.Graphics;
 using osu.Framework.Extensions.Color4Extensions;
@@ -25,7 +26,7 @@ using Container = osu.Framework.Graphics.Containers.Container;
 
 namespace osu.Game.Overlays.Settings.Sections.General
 {
-    public class LoginSettings : FillFlowContainer, IOnlineComponent
+    public class LoginSettings : FillFlowContainer
     {
         private bool bounding = true;
         private LoginForm form;
@@ -41,6 +42,11 @@ namespace osu.Game.Overlays.Settings.Sections.General
         /// </summary>
         public Action RequestHide;
 
+        private readonly IBindable<APIState> apiState = new Bindable<APIState>();
+
+        [Resolved]
+        private IAPIProvider api { get; set; }
+
         public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty;
 
         public bool Bounding
@@ -61,17 +67,18 @@ namespace osu.Game.Overlays.Settings.Sections.General
             Spacing = new Vector2(0f, 5f);
         }
 
-        [BackgroundDependencyLoader(permitNulls: true)]
-        private void load(IAPIProvider api)
+        [BackgroundDependencyLoader]
+        private void load()
         {
-            api?.Register(this);
+            apiState.BindTo(api.State);
+            apiState.BindValueChanged(onlineStateChanged, true);
         }
 
-        public void APIStateChanged(IAPIProvider api, APIState state) => Schedule(() =>
+        private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
         {
             form = null;
 
-            switch (state)
+            switch (state.NewValue)
             {
                 case APIState.Offline:
                     Children = new Drawable[]
@@ -107,7 +114,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
                             Origin = Anchor.TopCentre,
                             TextAnchor = Anchor.TopCentre,
                             AutoSizeAxes = Axes.Both,
-                            Text = state == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ",
+                            Text = state.NewValue == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ",
                             Margin = new MarginPadding { Top = 10, Bottom = 10 },
                         },
                     };
diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs
index bccef3d9fe..b21bc49a11 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs
@@ -2,6 +2,7 @@
 // See the LICENCE file in the repository root for full licence text.
 
 using osu.Framework.Allocation;
+using osu.Framework.Bindables;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Effects;
@@ -14,10 +15,15 @@ using osuTK.Graphics;
 
 namespace osu.Game.Overlays.Toolbar
 {
-    public class ToolbarUserButton : ToolbarOverlayToggleButton, IOnlineComponent
+    public class ToolbarUserButton : ToolbarOverlayToggleButton
     {
         private readonly UpdateableAvatar avatar;
 
+        [Resolved]
+        private IAPIProvider api { get; set; }
+
+        private readonly IBindable<APIState> apiState = new Bindable<APIState>();
+
         public ToolbarUserButton()
         {
             AutoSizeAxes = Axes.X;
@@ -43,17 +49,22 @@ namespace osu.Game.Overlays.Toolbar
             });
         }
 
-        [BackgroundDependencyLoader(true)]
-        private void load(IAPIProvider api, LoginOverlay login)
+        [BackgroundDependencyLoader(permitNulls: true)]
+        private void load()
         {
-            api.Register(this);
+            apiState.BindTo(api.State);
+            apiState.BindValueChanged(onlineStateChanged, true);
+        }
 
+        [BackgroundDependencyLoader(true)]
+        private void load(LoginOverlay login)
+        {
             StateContainer = login;
         }
 
-        public void APIStateChanged(IAPIProvider api, APIState state)
+        private void onlineStateChanged(ValueChangedEvent<APIState> state)
         {
-            switch (state)
+            switch (state.NewValue)
             {
                 default:
                     Text = @"Guest";
diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs
index 27f774e9ec..e6abde4d43 100644
--- a/osu.Game/Screens/Multi/Multiplayer.cs
+++ b/osu.Game/Screens/Multi/Multiplayer.cs
@@ -29,7 +29,7 @@ using osuTK;
 namespace osu.Game.Screens.Multi
 {
     [Cached]
-    public class Multiplayer : OsuScreen, IOnlineComponent
+    public class Multiplayer : OsuScreen
     {
         public override bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.CursorVisible ?? true;
 
@@ -146,15 +146,24 @@ namespace osu.Game.Screens.Multi
             screenStack.ScreenExited += screenExited;
         }
 
+        private readonly IBindable<APIState> apiState = new Bindable<APIState>();
+
         [BackgroundDependencyLoader(true)]
         private void load(IdleTracker idleTracker)
         {
-            api.Register(this);
+            apiState.BindTo(api.State);
+            apiState.BindValueChanged(onlineStateChanged, true);
 
             if (idleTracker != null)
                 isIdle.BindTo(idleTracker.IsIdle);
         }
 
+        private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
+        {
+            if (state.NewValue != APIState.Online)
+                Schedule(forcefullyExit);
+        });
+
         protected override void LoadComplete()
         {
             base.LoadComplete();
@@ -199,12 +208,6 @@ namespace osu.Game.Screens.Multi
             Logger.Log($"Polling adjusted (listing: {roomManager.TimeBetweenListingPolls}, selection: {roomManager.TimeBetweenSelectionPolls})");
         }
 
-        public void APIStateChanged(IAPIProvider api, APIState state)
-        {
-            if (state != APIState.Online)
-                Schedule(forcefullyExit);
-        }
-
         private void forcefullyExit()
         {
             // This is temporary since we don't currently have a way to force screens to be exited
@@ -371,12 +374,6 @@ namespace osu.Game.Screens.Multi
             }
         }
 
-        protected override void Dispose(bool isDisposing)
-        {
-            base.Dispose(isDisposing);
-            api?.Unregister(this);
-        }
-
         private class MultiplayerWaveContainer : WaveContainer
         {
             protected override bool StartHidden => true;
diff --git a/osu.Game/Screens/Select/DifficultyRecommender.cs b/osu.Game/Screens/Select/DifficultyRecommender.cs
index 0dd3341a93..ff54e0a8df 100644
--- a/osu.Game/Screens/Select/DifficultyRecommender.cs
+++ b/osu.Game/Screens/Select/DifficultyRecommender.cs
@@ -15,7 +15,7 @@ using osu.Game.Rulesets;
 
 namespace osu.Game.Screens.Select
 {
-    public class DifficultyRecommender : Component, IOnlineComponent
+    public class DifficultyRecommender : Component
     {
         [Resolved]
         private IAPIProvider api { get; set; }
@@ -28,10 +28,13 @@ namespace osu.Game.Screens.Select
 
         private readonly Dictionary<RulesetInfo, double> recommendedStarDifficulty = new Dictionary<RulesetInfo, double>();
 
+        private readonly IBindable<APIState> apiState = new Bindable<APIState>();
+
         [BackgroundDependencyLoader]
         private void load()
         {
-            api.Register(this);
+            apiState.BindTo(api.State);
+            apiState.BindValueChanged(onlineStateChanged, true);
         }
 
         /// <summary>
@@ -72,21 +75,14 @@ namespace osu.Game.Screens.Select
             });
         }
 
-        public void APIStateChanged(IAPIProvider api, APIState state)
+        private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
         {
-            switch (state)
+            switch (state.NewValue)
             {
                 case APIState.Online:
                     calculateRecommendedDifficulties();
                     break;
             }
-        }
-
-        protected override void Dispose(bool isDisposing)
-        {
-            base.Dispose(isDisposing);
-
-            api?.Unregister(this);
-        }
+        });
     }
 }